Brian’s functional brain in lisp
Last week I saw a breathless headline on proggit about clojure and Brian’s functional brain: http://blog.bestinclass.dk/index.php/2009/10/brians-functional-brain/, written by Lau.
As a Common Lisp programmer, Clojure irritates me for various irrational reasons. As an exercise in breaking those down, I ported Lau’s 67 line program (which had no comments) to CL running on SBCL using asdf-installable libraries. I used lispbuilders-sdl for display and pcall for concurrency. I ended up with 115 lines, including comments and some significant differences in the program.
I went through a few revisions, initially trying to transliterate the code, looking at the fine clojure API docs to figure out what different things did. Then I gave up on that wrote more idiomatic (at least for me) lisp, but still resisted the urge to use iterate of alexandria. I wanted to have code that was as close to the bare language as possible, so I could make an apples-to-apples comparison. Now that the exercise is done, I think that goal was unattainable. It’s close, but the differences in the languages are significant, so it’s not an great comparison.
After the first round, I started diverging more from the Lau’s version, looking for higher FPS and nicer lisp. I ended up with a few major differences:
- I used a 2D array to represent the world, the Lau used a single long vector and I didn’t quite understand how it was determining adjacency
- I had a lot more functions to abstract out that data structure choice (ie: instead of calling aref everywhere, I made a get-cell function)
- Lau called pmap function to calculate each cell’s next value in parallel, and I used pcall to calculate the next whole world state while the main thread rendered.
- Lau drew boxes for each rendering loop, I made two SDL surfaces up front and blitted them in at the right spots
I spent a little under 4 hours playing with it, and a lot of that was reading documentation. I don’t think any conclusions can be made from this for a “common lisp vs clojure” flame war, these are both fairly throw-away pieces of code. I have no doubt that any experiences lisper or clojurer would find a lot of obvious improvements.
Some of my observations along the way:
- getting the lisp libraries to work (which I’ve done in the past) is probably harder than getting clojure working and using java libs.
- java libs look like a pain in the ass. This softens the “and you can use java libs!” selling point of clojure for me. They’re still java libs.
- The places where clojure calls java are kinda ugly, it’s a square peg in a round hole.
- clojure has a ton of lazy-evaluation semantics built into the language. In this case, that seemed to be a bad thing, and most of Lau’s code was calling some wrapper function to say “no really, I want you to actually do this”.
- Clojure has more syntax than I thought, using # % [ ] _ to mean different things (maybe in different contexts?).
- I’m not sure how the STM features I’ve heard a lot about come into play here, if at all.
- I should be asdf loading my libs in a nicer way, right now you need to evaluate those first lines, and then compile the file. I didn’t have the motivation to create an .asd file or finally learn how to use eval-when properly.
- I like long, descriptive function names. Some of the ones from clojure irriated me: doall, doto. It reminds me of arc a little.
- I was confused by the per-cell parallelism in the clojure version (I think clojure uses native threads in a threadpool). Pcall does the same thing, but I figured I’d be spending more time context switching than calculating, and it was getting late.
Anyhoo, a fun sunday evening.
Code is on github: http://github.com/ryepup/sandbox/blob/master/brain.lisp
Christophe Grand said,
October 5, 2009 @ 4:50 am
Hi Ryan,
Interesting read. On a minor point I think you misunderstood some part of the clojure code: it doesn’t use a single long vector but a seq of seqs (which were initialized with a vector of vectors) — each nested seq being a row. Thus the parallelism is per row and not per cell. (The inner map isn’t replaced by pmap.)
Tom Emerson said,
October 5, 2009 @ 6:54 am
In response to your observations about Java interop (2 and 3): think of the interface to Java as Clojure’s FFI. Whenever you interface Lisp to a non-Lisp language you end up with a capacitance mismatch that makes the interaction a bit awkward: you can say the same thing (for example) in Clozure Common Lisp’s Objective-C bridge.
Interacting with the Java GUI classes is particularly ugly because these APIs are heavily object-oriented and rely on interfaces. However, once you grasp the constructs it isn’t any more awkward than using CL-FFI, IMHO.
When I utilize Java libraries in my non-trivial Clojure programs I usually write an idiomatic wrapper around the “low-level” interface. Consider the Penumbra interface to OpenGL: http://github.com/ztellman/penumbra
Lau Jensen said,
October 5, 2009 @ 7:40 am
Hey Ryan,
Interesting translation. I’ve just added a new blog post covering some optimization strategies for Brians Brain, you can have a look if you like. (http://tinyurl.com/ydhq8wf)
RE your observations:
5. It’s not syntax, it’s syntactic sugar so if it doesn’t help you, just don’t use it.
6. STM not used at all, it’s a concurrency semantic for synchronized change, see the Scala vs Clojure part 2 or Dining Philosophers post for more on this.
Best regards,
Lau
ryan said,
October 5, 2009 @ 6:01 pm
@Christophe: ahh, ok, that makes more sense.
@Tom: Excellent point, I totally agree. That means it’s only a matter of time before clojure developers build up and share idiomatic wrapper libs.
@Lau: Interesting optimizations. In my previous experience with using lisp and SDL, the actual drawing was always my bottleneck, so I didn’t bother with profiling. I’m curious if I can add type declarations and get any noticeable improvement.
RE syntactic sugar, there are a few lisp libraries that add that sort of thing to the language, and they have mixed adoption. I prefer adding small functions (like get-cell / set-cell) to abstract things a little and I think it makes my code more readable.
RE STM, I vaugely recall some ant simulation example from a while back, and someone wrote it in lisp, but there seemed to be a major difference based on STM that I didn’t grok at the time. I suspected that might be the case here, too, but I didn’t see any STM in use.