Bonnie Eisenman bio photo

Bonnie Eisenman

Software engineer, author, knitter, Esperantist. Member of NYC Resistor and author of Learning React Native.

🐦 Twitter 🤖 Github 🔶 RSS Feed

I’m going to be participating in the Recurse Center one week mini-batch in January. Recurse Center describes itself as “a self-directed, community-driven educational retreat for programmers in New York City.” For the mini-batch, my proposed project was to 1. learn some lisp and 2. generate SVGs of jigsaw puzzles, for laser cutting.

Why these projects?

I’ve been wanting to learn Lisp for a while, because I thought that it would be a different kind of problem-solving experience - I like making my brain bend and the Lispers I know have different problem-solving instincts than me. I was also curious about it from a historical POV. Apparently it’s been around since the 1950s? Though I guess that depends on which dialect we’re talking about; Common Lisp, which is what I’m learning, dates from the 1980s.

Why generate SVGs? Because it’s relatively straightforward (I’ve done SVG generation before) and learning how to print things is a standard newbie-programmer task. Also, I adore generative art, and I love puzzles. I was heavily inspired by the work of inconvergent and nervous systems.

This post isn’t about jigsaw puzzles, though, it’s about my first experiences learning Lisp. Here are some jumbled thoughts!

I’m following The Land of Lisp. The book is…hrm. Interesting. I grumbled about it on Twitter. First of all, the illustrations are adorable.

The pedagogical approach of the book also works really well for me - I enjoyed following along with the beginning exercises. Ideologically, though, I’m a bit annoyed. The book spends a lot of time assuring you that Lisp is “beautiful” and “pure” and “elegant”, and also warning you that Lispers can be judgmental assholes, though it doesn’t use those words. Arrrgh.

The clisp REPL also includes this somewhat baffling ASCII art, which I kind of love:

Anyway, here are some unordered musings:

  • In Lisp, you can get the head of a list with a function named CAR (Contents of the Address Register) and the remainder with CDR (Contents of the Decrement Register). Look, they even have a Wiki page. I’m given to understand that many modern Lisps just call these FIRST and REST or some such English-language thing. I think it’s pretty interesting that 1. they compose well and 2. you can’t just get the nth element of a list.

  • Relatedly, implementing my own randselect function looked like this:

(defun randselect (somelist)
  (let
    ((n (length somelist)))
    (car (nthcdr (random n) somelist))
  )
)
  • How on earth does performance work in this language? I’m super curious.

  • I want to make tongue twisters out of cadddraddadar. No, seriously, it’s adorable that you would name a function after the actual registers involved but I think this is one of those, uh, historical quirks that shows Lisp’s age. Someone sent me a link to this HackerNews discussion which actually seemed quite reasonable all-around.

  • I’m dying to know how you write maintainable, shareable code in Lisp that lots of people can work on who aren’t all inside each other’s heads. The way that Land of Lisp is encouraging me to write code - where you basically have lots and lots of unnamed heterogeneous lists where each element does Something Special and functions expect things in some weird undocumented order - is EXACTLY how I like to write python scripts for my side projects. As came up in my RC pairing interview, however, that approach is extraordinarily bug prone. It also takes forever for someone new to learn it. So … I’m curious. But so far at least it matches how I think :) (Fast to write, infuriating to debug…)

  • These function names are a little weird. MAPCAR, really ??? I get it but also I’m coming in with pleeeenty of my own biases and this feels weird.

  • Evidently using APPLY is dangerous ?? But it’s so useful! Gah! I can’t wait til we get to tail recursion.

  • Someone suggested I switch to using sbcl for my REPL; apparently it is More Efficient. However it lacks clisp’s awesome autocomplete in the REPL, at least on Mac.

  • The usage of association lists (alists?) confuses me. Is there anything that distinguishes an alist? How do you tell if something can be used as an alist?

  • Figuring out how to print stuff is weird. String interpolation is confusing. I thought that FORMAT was the answer but then it always prints to screen instead of returning a value? Unless you pass “nil” as the first argument, apparently. Should I be using CONCATENATE? I ended up finding a useful resource here.

" If the string will be constructed out of (the printed representations of) arbitrary objects, (symbols, numbers, characters, strings, ...), you can use FORMAT with an output stream argument of NIL. This directs FORMAT to return the indicated output as a string. "
  • Random apparently doesn’t have a time-based seed ?? At least not by default. You have to seed it yourself, laŭ StackOverflow:
(setf *random-state* (make-random-state t))
  • The syntax for LET is weird. I was confused about how to set up a variable that depends on another variable, e.g. a = 5, b = a + 1. It turns out that you need to use LET* for this? I got this from Practical Common Lisp.
(let* ((x 10)
       (y (+ x 10)))
  (list x y))

At the end of my very early flailings about, I’ve made it through six out of twenty chapters of Land of Lisp and written my very first basic script. It’s adapted from my SET bot Python code, and right now it generates an SVG with a randomly-colored circle.

;;;; sbcl --script setsvg.lisp > foo.svg
;;;; Creates an SVG with a randomly-colored circle.

; Initialize the global random state.
; https://stackoverflow.com/questions/4034042/random-in-common-lisp-not-so-random
(setf *random-state* (make-random-state t))

(defparameter *begin-svg* "<svg xmlns=\"http://www.w3.org/2000/svg\"
  xmlns:xlink=\"http://www.w3.org/1999/xlink\"
  width=\"500\" height=\"500\">")

(defparameter *end-svg* "</svg>")

(defun ellipse (color)
  (concatenate 'string "<ellipse
      cx=\"250.0\"
      cy=\"250.0\"
      rx=\"100.0\"
      ry=\"100.0\"
      stroke=\"#fc8d62\"
      stroke-width=\"5\"
      fill=\"#" color "\"
    />")
  )

(defparameter *colors* '("66c2a5" "fc8d62" "8da0cb"))

(defun randselect (somelist)
  (let
    ((n (length somelist)))
    (car (nthcdr (random n) somelist))
  )
)

(defun randcolor ()
  (randselect *colors*)
)

(defun svg (body)
  (format t "~d" *begin-svg*)
  (format t "~d" body)
  (format t "~d" *end-svg*)  
)

(defun randcircle ()
  (svg (ellipse (randcolor)))
)

(randcircle)