2010-05-08 15 views
11

Después de algunos fines de semana explorando Clojure, se me ocurrió este programa. Te permite mover un pequeño rectángulo en una ventana. Aquí está el código:Mejorando mi primer programa Clojure

(import java.awt.Color) 
(import java.awt.Dimension) 
(import java.awt.event.KeyListener) 
(import javax.swing.JFrame) 
(import javax.swing.JPanel) 

(def x (ref 0)) 
(def y (ref 0)) 

(def panel 
    (proxy [JPanel KeyListener] [] 
    (getPreferredSize [] (Dimension. 100 100)) 
    (keyPressed [e] 
     (let [keyCode (.getKeyCode e)] 
     (if (== 37 keyCode) (dosync (alter x dec)) 
     (if (== 38 keyCode) (dosync (alter y dec)) 
     (if (== 39 keyCode) (dosync (alter x inc)) 
     (if (== 40 keyCode) (dosync (alter y inc)) 
          (println keyCode))))))) 
    (keyReleased [e]) 
    (keyTyped [e]))) 

(doto panel 
    (.setFocusable true) 
    (.addKeyListener panel)) 

(def frame (JFrame. "Test")) 
(doto frame 
    (.add panel) 
    (.pack) 
    (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) 
    (.setVisible true)) 

(defn drawRectangle [p] 
    (doto (.getGraphics p) 
    (.setColor (java.awt.Color/WHITE)) 
    (.fillRect 0 0 100 100) 
    (.setColor (java.awt.Color/BLUE)) 
    (.fillRect (* 10 (deref x)) (* 10 (deref y)) 10 10))) 

(loop [] 
    (drawRectangle panel) 
    (Thread/sleep 10) 
    (recur)) 

pesar de ser un programador de C++ con experiencia Me pareció muy difícil de escribir incluso una sencilla aplicación en un idioma que utiliza un estilo radicalmente diferente a lo que estoy acostumbrado.

Además de eso, este código probablemente es una mierda. Sospecho que la globalidad de los diversos valores es algo malo. Tampoco es claro para mí si es apropiado usar referencias aquí para los valores xey.

Cualquier sugerencia para mejorar este código es bienvenido.

+2

estoy trabajando en clojure aprendizaje también. Gracias por la pregunta y la muestra del código de trabajo. – mcotton

+0

@mcotton, me alegra que lo encuentre útil. Quizás estas notas también sean útiles: http://www.reddit.com/r/programming/comments/c16rr/clojure_notes/ – StackedCrooked

+0

¡Me encanta este programa! Cuando lo ejecuto bajo leiningen, de vez en cuando obtengo 'Exception in thread' AWT-EventQueue-0 "java.lang.IllegalArgumentException: No hay una cláusula que coincida: 157', ya sea que se ejecute mediante' lein run' o mediante 'lein uberjar'. No tengo idea de dónde viene este error. –

Respuesta

12

Esos if s en keyPressed se pueden reemplazar por un solo case. Además, el dosync se puede mover al exterior para envolver el case. De hecho, alter se puede mover también, por lo que si p. decida cambiarlo a commute, solo hay un lugar para hacer el cambio. El resultado:

(def panel 
    (proxy [JPanel KeyListener] [] 
    (getPreferredSize [] (Dimension. 100 100)) 
    (keyPressed [e] 
     (let [keyCode (.getKeyCode e)] 
     (dosync 
     (apply alter 
      (case keyCode 
      37 [x dec] 
      38 [y dec] 
      39 [x inc] 
      40 [y inc]))) 
     (println keyCode))) 
    (keyReleased [e]) 
    (keyTyped [e]))) 

También podría reescribir las importaciones de manera más concisa:

(import [java.awt Color Dimension event.ActionListener]) 
(import [javax.swing JFrame JPanel]) 

- ya sea que querría es una cuestión de estilo.

Cambiaría el nombre a drawRectangle por draw-rectangle (ese es el estilo idiomático para los nombres de funciones en Clojure) y, más concretamente, lo reescribo como una función pura que acepta las coordenadas como argumentos explícitos. Luego, podría escribir un pequeño contenedor para usar sus Refs, si es que su diseño se beneficiaría del uso de Refs. (Es difícil de decir sin saber cómo es posible que desee utilizar & las modifican etc.)

Prefiero while a (loop [] ... (recur)) (ver (doc while) y (clojure.contrib.repl-utils/source while)).

Además, y esto es importante, no coloque nada excepto las definiciones en el nivel superior. Esto se debe a que los formularios de nivel superior se ejecutan cuando se compila el código (intente cargar una biblioteca con un (println :foo) en el nivel superior). Ese bucle infinito debería envolverse dentro de una función; el nombre estándar para una función "principal" en Clojure es -main; lo mismo vale para panel y frame. Esto no se aplica cuando se juega en el REPL, por supuesto, pero es importante saberlo.

Por cierto, (doto foo ...) devuelve foo, por lo que puede escribir (doto (proxy ...) (.setFocusable true) ...).

De lo contrario, diría que el código está bien. Normalmente querrías ponerlo en un espacio de nombres; entonces todas las importaciones irían en el formulario ns.

HTH

+0

Gracias, esto es útil. – StackedCrooked

+0

Creo que o bien tiene que definir su propio 'case' o ir con' (condp = keyCode ...) '. ¿O hay un 'caso' en alguna parte de las bibliotecas estándar de Clojure? –

+0

Oh, aparentemente se ha agregado después de 1.1. Gracias por atrapar eso! Y, por supuesto, la solución correcta para 1.1 es usar '(condp = ...)' (incluso en 1.2 si hay colisiones hash entre expresiones de prueba, ver http://www.assembla.com/spaces/clojure/ entradas/296). –

3

Aunque no exactamente consejos Clojure - Considere el uso de KeyAdapter en lugar de KeyListener. De esta forma, no tendrá que proporcionar implementaciones vacías para keyReleased y keyTyped. En general, es una buena regla usar clases de adaptadores cuando no se necesitan todos los métodos de una interfaz de Listener.

5

Editar, mientras escribo la publicación de abajo con mucha prisa, me olvidé de decir que no debe poner paréntesis alrededor de las constantes, por ejemplo, java.awt.Color/WHITE.

Además de los comentarios de Michal:

  • cuando se utiliza un bucle infinito, acondicionarlo con un átomo (por ejemplo @switch) por lo tanto usted será capaz de detenerlo (a menos que los bucles se ejecuta en el hilo repl - así que en un hilo de separación con "futuro")

  • utiliza refs, por lo que significa que los valores de xey están destinados a coordinarse (en su código no están: cada actualización es independiente); por lo tanto, en drawRectangle debes ajustar las lecturas en dosync para asegurarte de que obtienes una vista coherente. De nuevo, en tu código actual no importa porque xey siempre se actualizan de forma independiente.

HTH,

(enchufe descarado: http://conj-labs.eu/)

Cuestiones relacionadas