This is related to the thread titled "Proxying in Clojure". I've spent
a lot of time studying the code in this "snake" example and have some
observations I'd like to share.

First off, obviously I care about the future of Clojure or I wouldn't
have spent the last six weeks or so trying to learn it.

I imagine that almost everyone on this mailing list would like to have
the opportunity to spend more time on the job coding in Clojure than
they get to currently. One way to make that more likely is to make it
easier for others to learn Clojure. You may not be allowed to use it
for production code if you're the only one in your company that can
understand it.

Many examples of Clojure code work counter to this goal. This snake
code is just one of them. I don't mean to pick on the authors. This
code is very similar to other Clojure code I encounter daily.

Here are some things I think we could all do in our Clojure code to
make it easier for others to understand.

1) There are no prizes for using one letter variable names. Pick more
meaningful names.

For example, (defn collision? [{[b] :body} a] ...).
What is a? It's a vector containing the x/y coordinates of the apple
the snake is trying to eat. Why not name it "apple" or
"apple-location"?
What is b? It's a vector that represents the x/y coordinates of the
head of the snake.
Why not name it "head" or "snake-head"?

2) There are no prizes for writing code with zero comments. One of the
strengths of Clojure is that you can accomplish a large amount in a
small amount of code. That doesn't mean that readers of your code will
know what is going on though.

For example, what does this do?
(every? #(<= (- (a %) 10) (b %) (+ 10 (a %))) [0 1])
Well, 0 and 1 are indexes of the x and y coordinates represented by
two element vectors that represent the location of the apple and the
head of the snake. This tests whether every coordinate (the x and y)
of the apple are "close" to the corresponding coordinate of the snake
head. This certainly needs to be explained in a comment.

3) There are no prizes for cramming loads of functionality into a
single line. Spread it out to make it easier to read.

For example, which of these is easier to understand?

; original
 (assoc snake :body (cons (vec (map #(+ (dir %) ((first body) %)) [0 1]))
                          (if grow body (butlast body)))))

What is being cons'ed to what here?

; modified
  (assoc snake :body
    (cons
      (vec (map #(+ (dir %) ((first body) %)) [0 1]))
      (if grow body (butlast body))
    )
  )

I know my placement of the closing parens on separate lines is
non-standard in the Lisp world, but I find it helps me see better
where constructs end. Without doing that it feels like Python where
indentation is significant. I could concede the paren placement
though.

Below is the original code. Compare it to my version at
http://pastie.org/348031 where variables are renamed, comments are
added, and indentation is changed in a way that I feel makes it more
readable.

(import '(java.awt Color) '(javax.swing JPanel JFrame Timer)
       '(java.awt.event KeyEvent ActionListener KeyListener))

(defn gen-apple [_] [(rand-int 750) (rand-int 550)])
(defn move [{:keys [body dir] :as snake} & grow]
 (assoc snake :body (cons (vec (map #(+ (dir %) ((first body) %)) [0 1]))
                          (if grow body (butlast body)))))
(defn turn [snake newdir] (if newdir (assoc snake :dir newdir) snake))
(defn collision? [{[b] :body} a]
 (every? #(<= (- (a %) 10) (b %) (+ 10 (a %))) [0 1]))
(defn paint [g p c] (.setColor g c) (.fillRect g (p 0) (p 1) 10 10))

(def dirs {KeyEvent/VK_LEFT [-10 0] KeyEvent/VK_RIGHT [10 0]
          KeyEvent/VK_UP   [0 -10] KeyEvent/VK_DOWN  [0 10]})
(def apple (atom (gen-apple nil)))
(def snake (atom {:body (list [10 10]) :dir [10 0]}))
(def colors {:apple (Color. 210 50 90) :snake (Color. 15 160 70)})
(def panel (proxy [JPanel ActionListener KeyListener] []

            (paintComponent [g] (proxy-super paintComponent g)
                            (paint g @apple (colors :apple))
                            (doseq [p (:body @snake)]
                              (paint g p (colors :snake))))
            (actionPerformed [e] (if (collision? @snake @apple)
                                   (do (swap! apple gen-apple)
                                       (swap! snake move :grow))
                                   (swap! snake move))
                             (.repaint this))
            (keyPressed [e] (swap! snake turn (dirs (.getKeyCode e))))
            (keyReleased [e])
            (keyTyped [e])))


(doto panel (.setFocusable true)(.addKeyListener panel))
(doto (JFrame. "Snake")(.add panel)(.setSize 800 600)(.setVisible true))
(.start (Timer. 75 panel))

Another example of a fairly short piece of code that is begging for
comments is the example at http://clojure.org/concurrent_programming.
Imagine someone who has only been learning Clojure for a couple of
days and is curious about how Clojure handles concurrency trying to
follow what is happening in that code.

So my main point is that if we all make an effort to make our code
easier to read, it will be easier to convince other developers to
consider learning Clojure which will be good for all of us.

-- 
R. Mark Volkmann
Object Computing, Inc.

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to 
clojure+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to