改进我的第一个Clojure计划

时间:2010-05-08 00:37:00

标签: clojure

经过几个周末探索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))

尽管我是一名经验丰富的C ++程序员,但我发现用一种语言编写一个简单的应用程序是非常具有挑战性的,这种语言使用的风格与我以前完​​全不同。

最重要的是,这段代码可能很糟糕。我怀疑各种价值观的全球性是一件坏事。我也不清楚在这里使用x和y值的引用是否合适。

欢迎任何改进此代码的提示。

3 个答案:

答案 0 :(得分:12)

if中的keyPressedcase可以替换为单个dosync。此外,case可以移到外面以包裹alter。事实上,commute也可以移出,所以如果你是决定将其更改为(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]))) ,只有一个地方可以进行更改。结果:

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

您还可以更简洁地重写导入:

drawRectangle

- 你是否想要的是风格问题。

我会将draw-rectangle重命名为while(这是Clojure中函数名称的惯用风格),更重要的是,将其重写为接受坐标作为显式参数的纯函数。那么你可以在它周围写一个小包装来使用你的Refs,如果你的设计确实会受益于Refs的使用。 (很难说你不知道如何使用和修改它们等。)

首选(loop [] ... (recur))(doc while)(请参阅(clojure.contrib.repl-utils/source while)(println :foo))。

此外 - 这一点非常重要 - 除了顶级定义之外不要放任何东西。这是因为在编译代码时实际执行了顶级表单(尝试在顶层加载-main的库)。那个无限循环应该包含在一个函数中; Clojure中“main”函数的标准名称是panel;同样适用于frame(doto foo ...)。当然,在REPL玩游戏时不适用,但这是一个重要的问题。

顺便说一下,(doto (proxy ...) (.setFocusable true) ...)会返回foo,因此您只需撰写ns

否则,我会说代码没问题。通常你想把它放在命名空间里;然后所有导入都将以{{1}}形式出现。

HTH

答案 1 :(得分:5)

编辑,在匆忙写下面的帖子时,我忘了说你不能把常量放在常量上,例如java.awt.Color / WHITE。

除了Michał的评论:

  • 当你使用无限循环时,用一个原子来调节它(例如@switch),这样你就能够阻止它(除非循环在repl线程上运行 - 所以它在一个分离的线程中)与“未来”)

  • 你使用refs,所以它意味着要协调x和y的值(在你的代码中它们不是:每次更新都是独立的);因此,在drawRectangle中,您应该将读取包装在dosync中,以确保获得一致的视图 - 再次在实际代码中,它无关紧要,因为x和y总是独立更新。

HTH,

(无耻的插件:http://conj-labs.eu/

答案 2 :(得分:3)

虽然不完全是Clojure建议 - 考虑使用KeyAdapter而不是KeyListener。这样您就不必为keyReleased和keyTyped提供空实现。当您不需要Listener接口的所有方法时,使用适配器类通常是一个很好的规则。