Skip to content

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:

  1. 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
  2. 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)
  3. 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.
  4. 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:

  1. getting the lisp libraries to work (which I’ve done in the past) is probably harder than getting clojure working and using java libs.
  2. 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.
  3. The places where clojure calls java are kinda ugly, it’s a square peg in a round hole.
  4. 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”.
  5. Clojure has more syntax than I thought, using # % [ ] _ to mean different things (maybe in different contexts?).
  6. I’m not sure how the STM features I’ve heard a lot about come into play here, if at all.
  7. 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.
  8. I like long, descriptive function names.  Some of the ones from clojure irriated me: doall, doto.  It reminds me of arc a little.
  9. 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

4 Comments

  1. 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.)

    Monday, October 5, 2009 at 4:50 am | Permalink
  2. Tom Emerson wrote:

    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

    Monday, October 5, 2009 at 6:54 am | Permalink
  3. Lau Jensen wrote:

    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

    Monday, October 5, 2009 at 7:40 am | Permalink
  4. ryan wrote:

    @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.

    Monday, October 5, 2009 at 6:01 pm | Permalink