2010-05-08 58 views
11

在几个周末探索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值的引用是否合适。

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

+2

我正在学习Clojure的了。感谢问题和工作代码示例。 – mcotton 2010-05-08 04:00:52

+0

@mcotton,很高兴你觉得它有帮助。也许这些笔记也很有用:http://www.reddit.com/r/programming/comments/c16rr/clojure_notes/ – StackedCrooked 2010-05-08 14:26:30

+0

我喜欢这个程序!当在leiningen下运行时,我偶尔会得到'线程异常AWT-EventQueue-0'java.lang.IllegalArgumentException:没有匹配的子句:157',无论是通过lein run还是通过lein uberjar运行它。我不知道这个错误来自哪里。 – 2013-03-24 14:36:26

回答

12

if s在keyPressed可以被替换为单个case。另外,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(这是Clojure中函数名的惯用风格),更重要的是,将它重写为接受坐标作为显式参数的纯函数。然后你可以写一个小的包装来使用你的参考,如果你的设计确实会受益于参考。 (很难不知道你怎么可能希望使用&对其进行修改等说吧)

不想while(loop [] ... (recur))(见(doc while)(clojure.contrib.repl-utils/source while))。

另外 - 这是很重要的 - 除了定义顶级以外不要放任何东西。这是因为在编译代码时实际执行顶层窗体(尝试在顶层加载一个带有(println :foo)的库)。该无限循环应该包装在一个函数中; Clojure中“主”功能的标准名称是-main; panelframe也是如此。这当然不适用于在REPL玩耍,但这是一个重要的知识。

顺便提一句,(doto foo ...)返回foo,所以你可以写(doto (proxy ...) (.setFocusable true) ...)

否则,我会说代码是好的。通常你会想把它放在一个名字空间中;那么所有的进口都将以ns的形式出现。

HTH

+0

谢谢,这很有帮助。 – StackedCrooked 2010-05-08 02:31:33

+0

我认为你要么定义你自己的'case'或者用'(condp = keyCode ...)'去。还是在标准的Clojure库中有一个'case'? – 2010-05-13 16:11:26

+0

哦,显然它已被添加到1.1后。感谢您的支持!当然,1.1的正确解决方案确实是使用'(condp = ...)'(实际上即使在1.2中,如果测试表达式之间存在散列冲突,请参阅http://www.assembla.com/spaces/clojure/门票/ 296)。 – 2010-05-13 16:38:28

3

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

5

编辑,虽然赶时间写下面的文章,但我忘了说你不能把常量放在例如java.awt.Color/WHITE。

除了Michal的评论:

  • 当您使用一个无限循环,用原子调理它(例如@switch)这样你就可以阻止它(除非环路上运行你可以使用refs,所以它意味着x和y的值是协调一致的(在你的代码中它们不是:每个更新都是独立);因此,在drawRectangle中,您应该将读取操作包装在dosync中以确保您获得一致的视图 - 再次在您的实际代码中,这并不重要,因为x和y总是独立更新。

心连心,

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