Tuesday, July 28, 2009

Clojure and lisp

I guess this isn't really a gotcha but I seriously just spent four hours staring at a piece of code which seemed to me to be perfectly correct and just wouldn't behave in the weirdest way. This is the load tester so far:

(import '(com.sun.sgs.client.simple SimpleClient SimpleClientListener))
(import '(com.sun.sgs.client ClientChannelListener))
(import '(java.util Properties Timer TimerTask))
(import '(java.net PasswordAuthentication))
(import '(java.nio ByteBuffer))
(def unicode "UTF-8")
(def package (let [s (slurp "client.clj")](str s s s s s s s s s s s s s s s s s s s s s)))
(defmacro counter [label limit]
`(let [count# (ref 0)]
(fn[]
(dosync (alter count# inc)
(if(=(rem(deref count#) ~limit) 0)
(prn ~label " " count#))))))
(defn props[]
(let [properties (new Properties)]
(. properties put "host" "localhost")
(. properties put "port" "1139")
properties))
(defn decode [buffer]
(let [bytes (make-array (. Byte TYPE) (. buffer remaining))]
(. buffer get bytes)
(new String bytes unicode)))
(defn encode [string]
(ByteBuffer/wrap (. string getBytes unicode)))
(def received (counter "Received" 500))
(def requested (counter "Requested" 25))
(defn client [i]
(let [username (format "username%s" i)]
(new SimpleClient (proxy [SimpleClientListener ClientChannelListener][]
(loggedIn[] (println (format "Logged in %s" username)))
(loginFailed[reason] (println "Login failed: " reason username))
(getPasswordAuthentication[](new PasswordAuthentication username (. "unusedPassword" toCharArray)))
(receivedMessage([message](prn(format "%s Received message" username)))
([channel, message](received)))
(joinedChannel[channel] this)
(leftChannel[channel] (println "Left channel"))
(disconnected[graceful? reason] (println "Disconnected: " reason graceful?))))))
(defn publish[who what]
(do
(. who send (encode (str "/PUBLIC_MESSAGE " what))))
(requested))

So as you can see, quite succinct given what it does which is fairly powerful. Also a few bits that definitely need improvement (that construction around slurping in the file and building it up to be big enough is retarded), which I'm taking a bit at a time. The most recent revision was to build the counter macro, which is this bit:

(defmacro counter [label limit]
`(let [count# (ref 0)]
(fn[]
(dosync (alter count# inc)
(if(=(rem(deref count#) ~limit) 0)
(prn ~label " " count#))))))

Pretty complex looking, at least to me. It's job is to supply me a function which will increment a thread safe counter, printing out a defined message when the counter hits a multiple of a particular value. This is to minimize the flood of output that tends to characterize my naive solutions.

What was the thing that took four hours? Surprisingly, it wasn't the macro itself. They're fairly logical - just splicing values in at compile time, into code templates. If you've done pretty much any kind of interpolation - XSLT, Velocity, whatever - you've done this before. I don't deny that it gets complicated when macros refer to macros, but it still seems that if you're careful it just works out ok. Note, for instance, that I've deliberately declared my let binding for the actual count integer to be gensymed. (For the non lispers out there, gensyming is telling lisp to find me a symbol which can't possibly conflict with or redefine anything already in existence, because it's from a unique and nonreachable namespace). This, as per the parenthetical, means that it won't conflict. Which is nice.

The thing that took me four hours was that this:
`
is not the same as this:
'

And only one of those actually means quotation. Bugger.