Proxying in clojure

61 views
Skip to first unread message

MattyDub

unread,
Dec 23, 2008, 5:19:34 PM12/23/08
to Clojure
On the Java Interop page (http://clojure.org/java_interop), the proxy
macro is described as creating "...a instance of a proxy class that
implements the named class/interface(s)...". Is this done using Java
Dynamic Proxy Classes (http://java.sun.com/j2se/1.5.0/docs/guide/
reflection/proxy.html)?

Background to this question: I came across a blog post (http://
www.plt1.com/1070/even-smaller-snake/) which claims to implement the
game "Snake" in 35 lines of clojure. However, it doesn't run; for one
thing, in line 26, the syntax for doseq is wrong. Another problem has
to do with calling methods from the JPanel class on the proxied JPanel
object created in the run-snake function. It looks like you shouldn't
call methods on proxied classes that aren't the ones you supplied when
creating the proxy - is that true?
Thanks for your time,
-Matt

Stephen C. Gilardi

unread,
Dec 23, 2008, 6:08:53 PM12/23/08
to clo...@googlegroups.com
On Dec 23, 2008, at 5:19 PM, MattyDub wrote:

On the Java Interop page (http://clojure.org/java_interop), the proxy
macro is described as creating "...a instance of a proxy class that
implements the named class/interface(s)...".  Is this done using Java
Dynamic Proxy Classes (http://java.sun.com/j2se/1.5.0/docs/guide/
reflection/proxy.html)?

That seems likely but I don't know for sure. The implementation is in the Clojure distribution in src/clj/clojure/core_proxy.clj and supporting classes in src/jvm/clojure/lang.

   Background to this question: I came across a blog post (http://
www.plt1.com/1070/even-smaller-snake/) which claims to implement the
game "Snake" in 35 lines of clojure.  However, it doesn't run; for one
thing, in line 26, the syntax for doseq is wrong.  Another problem has
to do with calling methods from the JPanel class on the proxied JPanel
object created in the run-snake function.  It looks like you shouldn't
call methods on proxied classes that aren't the ones you supplied when
creating the proxy - is that true?

It's not true. In addition to the update to doseq's syntax, the code there also needs an update to the new "doto" syntax. Now, when calling methods on a Java object instance inside a "doto" form, the method names need to be prefixed with ".":

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

The code in the blog post worked for me with today's Clojure after I made those two updates.

--Steve

MattyDub

unread,
Dec 23, 2008, 6:36:04 PM12/23/08
to Clojure
Ah, I didn't know about the doto change, thanks Stephen. It runs now,
although it doesn't seem to catch any of my key presses.
Thanks,
-Matt
>  smime.p7s
> 3KViewDownload

wubbie

unread,
Dec 23, 2008, 9:28:40 PM12/23/08
to Clojure
Hi,
I ran it with the changes and got the message:
java.lang.IllegalArgumentException: Don't know how to create ISeq
from: Symbol (NO_SOURCE_FILE:22)

What can be the problem?
thanks
sun
> smime.p7s
> 3KViewDownload

Stephen C. Gilardi

unread,
Dec 23, 2008, 9:42:30 PM12/23/08
to clo...@googlegroups.com

On Dec 23, 2008, at 9:28 PM, wubbie wrote:

> Hi,
> I ran it with the changes and got the message:
> java.lang.IllegalArgumentException: Don't know how to create ISeq
> from: Symbol (NO_SOURCE_FILE:22)
>
> What can be the problem?

I recommend you try this:

- copy and paste the code from the website into a file called
snake.clj stored at the top level of a directory that's in classpath.
- replace the "import" form at the top of the page with this (be
careful with quoting in this edit):

(ns snake
(:import (java.awt.event KeyEvent ActionListener KeyListener)
(javax.swing JPanel JFrame Timer)))

- fix the doseq and doto forms

Then, at the repl run it with:

user=> (require 'snake)
nil
user=> (snake/run-snake)

Click on the window that comes up and use the arrow keys at the
keyboard to control the snake.

If you still have trouble, please post info about it here. At that
point we should have more to work with.

--Steve

Abhishek Reddy

unread,
Dec 23, 2008, 9:42:46 PM12/23/08
to clo...@googlegroups.com
Hi,

That was written several months ago, against a very different version
of Clojure.

Below is a working port, against a current Clojure (rv 1185), on
OpenJDK (1.6.0_0-b11) on GNU/Linux. The original web page will also
be updated.

(ns snake (:import (java.awt.event KeyEvent ActionListener KeyListener)
(javax.swing JPanel JFrame Timer)))

(defn gen-apple [] {:x (int (* 790 (Math/random))) :y (int (* 590
(Math/random)))})

(def snake (ref {:body (list {:x 10 :y 10}) :dir {:x 10 :y 0}}))
(def apple (ref (gen-apple)))
(def dirs {KeyEvent/VK_LEFT {:x -10 :y 0} KeyEvent/VK_RIGHT {:x 10 :y 0}
KeyEvent/VK_UP {:x 0 :y -10} KeyEvent/VK_DOWN {:x 0 :y 10}})

(defn move [{body :body dir :dir :as snake} & opts]
(merge snake {:body (cons {:x (+ (:x dir) (:x (first body)))
:y (+ (:y dir) (:y (first body)))}
(if (:grow (apply hash-map opts)) body
(butlast body)))}))

(defn turn [snake newdir] (if newdir {:body (:body snake) :dir newdir} snake))

(defn collision? [{[b & bs] :body} a]
(and (>= (+ 10 (:x b)) (:x a)) (<= (:x b) (+ 10 (:x a)))
(>= (+ 10 (:y b)) (:y a)) (<= (:y b) (+ 10 (:y a)))))

(defn run-snake []
(let [panel (proxy [JPanel ActionListener KeyListener] []
(paintComponent [g] (proxy-super paintComponent g)
(.fillRect g (:x @apple) (:y @apple) 10 10)
(doseq [p (:body @snake)]
(.fillRect g (:x p) (:y p) 10 10))
(if (collision? @snake @apple)
(dosync (ref-set apple (gen-apple))
(alter snake move :grow true))))
(actionPerformed [e] (dosync (alter snake move))
(. this (repaint)))
(keyPressed [e] (dosync (alter snake turn (get dirs
(.getKeyCode e))))))]
(doto panel (.setFocusable true) (.addKeyListener panel))
(doto (new JFrame "Snake") (.add panel) (.setSize 800 600)
(.setVisible true))
(.start (new Timer 75 panel))))


Cheers,
Abhishek
--
Abhishek Reddy
http://abhishek.geek.nz

Chouser

unread,
Dec 23, 2008, 9:50:40 PM12/23/08
to clo...@googlegroups.com
I've also updated the code for "modern" Clojure, including stylistic
changes, plus some other changes that turned out to be mostly
gratuitous. Anyway, here it is. To run:

java -cp clojure.jar clojure.lang.Repl snake.clj

--Chouser

snake.clj

wubbie

unread,
Dec 23, 2008, 11:08:04 PM12/23/08
to Clojure
still have error:

sun@wubbie:~/clj-ex$ java -cp clojure.jar clojure.lang.Repl snake.clj
java.lang.IllegalArgumentException: Don't know how to create ISeq
from: Symbol (snake.clj:26)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4113)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyze(Compiler.java:3908)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:
3613)
at clojure.lang.Compiler$FnMethod.parse(Compiler.java:3456)
at clojure.lang.Compiler$FnMethod.access$1100(Compiler.java:
3335)
at clojure.lang.Compiler$FnExpr.parse(Compiler.java:2921)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4104)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4094)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyze(Compiler.java:3908)
at clojure.lang.Compiler$MapExpr.parse(Compiler.java:2553)
at clojure.lang.Compiler.analyze(Compiler.java:3939)
at clojure.lang.Compiler.analyze(Compiler.java:3908)
at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:2736)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4108)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyze(Compiler.java:3908)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:
3613)
at clojure.lang.Compiler$LetExpr$Parser.parse(Compiler.java:
3735)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4106)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4094)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4094)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.access$100(Compiler.java:38)
at clojure.lang.Compiler$LetExpr$Parser.parse(Compiler.java:
3724)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4106)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4094)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyze(Compiler.java:3908)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:
3613)
at clojure.lang.Compiler$FnMethod.parse(Compiler.java:3456)
at clojure.lang.Compiler$FnMethod.access$1100(Compiler.java:
3335)
at clojure.lang.Compiler$FnExpr.parse(Compiler.java:2921)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4104)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4094)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.access$100(Compiler.java:38)
at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:
366)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4106)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4094)
at clojure.lang.Compiler.analyze(Compiler.java:3935)
at clojure.lang.Compiler.analyze(Compiler.java:3908)
at clojure.lang.Compiler.eval(Compiler.java:4146)
at clojure.lang.Compiler.load(Compiler.java:4470)
at clojure.lang.Compiler.loadFile(Compiler.java:4437)
at clojure.lang.Repl.main(Repl.java:66)
Caused by: java.lang.IllegalArgumentException: Don't know how to
create ISeq from: Symbol
at clojure.lang.RT.seqFrom(RT.java:491)
at clojure.lang.RT.seq(RT.java:453)
at clojure.core$seq__28.invoke(core.clj:92)
at clojure.core$partition__810.invoke(core.clj:1477)
at clojure.core$partition__810.invoke(core.clj:1475)
at clojure.core$doseq__823.doInvoke(core.clj:1497)
at clojure.lang.RestFn.invoke(RestFn.java:443)
at clojure.lang.Var.invoke(Var.java:335)
at clojure.lang.AFn.applyToHelper(AFn.java:180)
at clojure.lang.Var.applyTo(Var.java:444)
at clojure.lang.Compiler.macroexpand1(Compiler.java:4025)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:4092)
... 52 more
Clojure
user=>

I just downloaded the latest one and while compiling with mvn
saw some warnings:
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered
resources, i.e. build is platform dependent!
Otherwise, compiled ok.

Cheers,
Sun
> snake.clj
> 2KViewDownload

wubbie

unread,
Dec 23, 2008, 11:21:57 PM12/23/08
to Clojure
Hi Steve,

It works now.

thanks
Sun
> smime.p7s
> 3KViewDownload

Stephen C. Gilardi

unread,
Dec 24, 2008, 1:44:03 AM12/24/08
to clo...@googlegroups.com
Based on Chouser's version, but using atoms, a splash of color, a
little more destructuring and more separation of model and view.

Very cool demo, Abhishek, thanks.

--Steve

(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))

Chouser

unread,
Dec 24, 2008, 8:11:30 AM12/24/08
to clo...@googlegroups.com
On Wed, Dec 24, 2008 at 1:44 AM, Stephen C. Gilardi <sque...@mac.com> wrote:
> Based on Chouser's version, but using atoms, a splash of color, a little
> more destructuring and more separation of model and view.

Would you be willing to explain why you think it's correct to use
atoms here? I considered it, but wasn't sure it was right. There are
two mutable objects, after all -- do they not need to be coordinated?

I realize that in a program like this it doesn't really matter, and
maybe that's part of my problem.

Great use of :grow instead of 'true' when calling 'move'. So sharp, Stephen!

--Chouser

Stephen C. Gilardi

unread,
Dec 24, 2008, 1:03:39 PM12/24/08
to clo...@googlegroups.com
On Dec 24, 2008, at 8:11 AM, Chouser wrote:

On Wed, Dec 24, 2008 at 1:44 AM, Stephen C. Gilardi <sque...@mac.com> wrote:
Based on Chouser's version, but using atoms, a splash of color, a little
more destructuring and more separation of model and view.

Would you be willing to explain why you think it's correct to use atoms here?  I considered it, but wasn't sure it was right.  There are two mutable objects, after all -- do they not need to be coordinated?

I actually hoped it would spark some discussion about atoms in view of recent threads that featured warnings about using atoms only very rarely. Here are my thoughts:

- Even in a simple game like this, it's important that the concurrency be handled in a correct way. (We don't want the snake flying apart over time, for example.) Persistent data structures handle most of that. Proper handling of the remaining mutable references provides the rest.

- I'm assuming that keyPressed, actionPerformed, and paintComponent are completely asynchronous with each other. Swing may make stronger promises, but I'm assuming the worst. I do assume that multiple calls to actionPerformed don't overlap each other since I know they happen at 75 ms intervals.

- Regarding keyPressed, in the case of a race with actionPerformed, the snake either "moves then turns" or "turns then moves". Those two orderings give results that are macroscopically equivalent in this game--the behavior will appear correct to the player either way.

- Regarding paintComponent
- atomic update and the fact that we (now) only dereference each of apple and snake once ensure that we will paint a consistent view of each. The former code dereferenced apple twice, once for each of its x and y coordinates--shouldn't that have been inside a dosync? The code using atoms dereferences apple once for both coordinates. The new paint function provided a natural way to do that: at argument evaluation time.
- in a race with actionPerformed, the apple might, for one call to paint, appear at its new location with an ungrown snake. We could come up with a "rules of the world" spec with promises about what the user could ever see that would be violated by this, but I don't see how it can interfere with game play. It does heal itself correctly and is unlikely to be noticed by a player. I believe that preventing this momentary, purely visual skew is the only coordination that the dosync would provide in this case.

Using dosync over atoms here would  allow the programmer to avoid much of the thought and analysis above. If one were to apply the rule "if there is more than one mutable object involved, use dosync to coordinate updates", the analysis would be much simpler and much less likely to give any surprises. I think it's a good way to go.

Great use of :grow instead of 'true' when calling 'move'.  So sharp, Stephen!

:-) Mysterious flag values of "true" have always bothered me in other languages. I like the code to speak for itself as much as possible. I'm glad you liked it!

--Steve

Emeka

unread,
Dec 25, 2008, 5:52:03 PM12/25/08
to clo...@googlegroups.com

 Snake frame would appear with square ball that moves and another that does not move.Below is  what I got each time I try to play with arrow keys. I count on you as always to come to my help.

user=> (require 'snake)
nil

user=> (snake/run-snake)nil

user=>
Exception in thread "AWT-EventQueue-0" java.lang.UnsupportedOperationException: keyReleased
 at clojure.proxy.javax.swing.JPanel$ActionListener$KeyListener.keyReleased(Unknown Source)
 at java.awt.Component.processKeyEvent(Unknown Source)
 at javax.swing.JComponent.processKeyEvent(Unknown Source)
 at clojure.proxy.javax.swing.JPanel$ActionListener$KeyListener.processKeyEvent(Unknown Source)
 at java.awt.Component.processEvent(Unknown Source)
 at java.awt.Container.processEvent(Unknown Source)
 at clojure.proxy.javax.swing.JPanel$ActionListener$KeyListener.processEvent(Unknown Source)
 at java.awt.Component.dispatchEventImpl(Unknown Source)
 at java.awt.Container.dispatchEventImpl(Unknown Source)
 at java.awt.Component.dispatchEvent(Unknown Source)
 at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source)
 at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(Unknown Source)
 at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(Unknown Source)
 at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source)
 at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
 at java.awt.Component.dispatchEventImpl(Unknown Source)
 at java.awt.Container.dispatchEventImpl(Unknown Source)
 at java.awt.Window.dispatchEventImpl(Unknown Source)
 at java.awt.Component.dispatchEvent(Unknown Source)
 at java.awt.EventQueue.dispatchEvent(Unknown Source)
 at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
 at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
 at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
 at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
 at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
 at java.awt.EventDispatchThread.run(Unknown Source)

Chouser

unread,
Dec 25, 2008, 7:23:20 PM12/25/08
to clo...@googlegroups.com
On Thu, Dec 25, 2008 at 5:52 PM, Emeka <emeka...@gmail.com> wrote:
> Exception in thread "AWT-EventQueue-0"
> java.lang.UnsupportedOperationException: keyReleased

It was to avoid this exception that my version included an empty
implementation of keyReleased.

--Chouser

Emeka

unread,
Dec 26, 2008, 4:40:03 AM12/26/08
to clo...@googlegroups.com
Chooser,

Please send me yours then.

Emeka


Chouser

unread,
Dec 26, 2008, 7:34:30 AM12/26/08
to clo...@googlegroups.com
On Fri, Dec 26, 2008 at 4:40 AM, Emeka <emeka...@gmail.com> wrote:
> Chooser,
"Chouser"

> Please send me yours then.

Already did:
http://groups.google.com/group/clojure/msg/4f00d2a3b5da8444

And Mr. Gilardi improved on it here:
http://groups.google.com/group/clojure/msg/90316675320091cf

--Chouser

MattyDub

unread,
Dec 26, 2008, 1:45:30 PM12/26/08
to Clojure
This might be off-topic, but when I launched "snake" from SLIME (via
load-file and then "run-snake"), the app didn't receive any UI
Events. I thought at first it might be because the snake Frame didn't
have focus, but even when I gave it focus, it still didn't receive any
Events. When I ran it from the clojure REPL (outside of SLIME), it
worked appropriately. Is this expected behavior from SLIME?
-Matt

On Dec 26, 4:34 am, Chouser <chou...@gmail.com> wrote:

Abhishek Reddy

unread,
Dec 26, 2008, 7:51:23 PM12/26/08
to clo...@googlegroups.com
Works for me from SLIME. Check your *inferior-lisp* buffer for exceptions.

Mark Volkmann

unread,
Dec 28, 2008, 12:35:17 PM12/28/08
to clo...@googlegroups.com
Can you explain what is happening in the collision? function?
I don't think I've seen % used outside the context of an anonymous function.

--
R. Mark Volkmann
Object Computing, Inc.

Randall R Schulz

unread,
Dec 28, 2008, 12:43:55 PM12/28/08
to clo...@googlegroups.com
On Sunday 28 December 2008 09:35, Mark Volkmann wrote:
> Can you explain what is happening in the collision? function?
> I don't think I've seen % used outside the context of an anonymous
> function.

To my eye, those uses of % _are_ within an anonymous function:

(defn collision? [{[b] :body} a]
(every? #(<= (- (a %) 10) (b %) (+ 10 (a %))) [0 1]))


Randall Schulz

Reply all
Reply to author
Forward
0 new messages