Feeling Lispy

Lately I’ve been reading a lot of stuff that praises Lisp. Stuff along the lines of:

  • Learning Lisp (in particular, reading “The Little Schemer”) will magically make you a better programmer. (Question: What if I buy the book and sleep with it under my pillow? I’m a bit busy now to read the book).
  • (Paul Graham, Richard Stallman, etc.) and many other smart people love Lisp. Therefore, if you want to be smart, you must learn Lisp.
  • C++ and Perl are bad. Lisp is not C++ and Perl. Therefore, Lisp is good.
  • Lisp is not a popular choice. Popular things are often not the best. Therefore, Lisp is the best.
  • A lisp is a speech impediment, which is a type of disability. Therefore, you aren’t allowed to say anything bad about Lisp.

I’m being a little bit cynical here, but the point is that I really did come across a lot of praise for Lisp recently from smart folks and started to wonder what I was missing, since I haven’t done Lisp since a programming languages class back in undergrad. And anything that might make me better at my craft is at least worth a try. So I resolved to revisit Lisp with an open mind…

Since I had Emacs handy and it would be nice to know more about how to extend Emacs, I mucked around a bit with elisp:

-*- lisp-interaction -*-

(defun join (alist sep)
  (cond
   ((= (length alist) 1) (car alist))
   (t (concat (car alist) sep (join (cdr alist) sep)))))

(join (list "a" "b" "c" "d") ":")

Emacs’s lisp-interaction-mode is pretty neat (kind of reminds me of Apple’s old MPW environment) and Lisp has a certain elegance in that there’s a lot of power in a very small set of primitives. Yet, while it’s true that everything can be represented as a list, it doesn’t strike me as the most readable way to represent most things. Ditto that on recursion. The code above looks awful to me – though perhaps I did it poorly or maybe I just haven’t acquired sufficient skill in reading Lisp. The parentheses and funky function names like “car” and “cdr” don’t do it for me (I wonder how confusing it must be to work on a Lisp program that deals with automobiles, such as a traffic simulation program)

Am I missing the key insight that unlocks all the wonders of Lisp? Is there a magic decoder ring? Should I look at Scheme, since it has more imperative language features?

Please, no flames. I’m just trying to understand Lisp better. If you love Lisp, let me know why…

Updates

  • 2006-03-11: Faried’s comments about first and rest in Common Lisp suggest this more readable implementation:
    (require 'cl)
    
    (defun join-2 (alist sep) 
      (if (null (rest alist)) 
          (first alist) 
        (concat (first alist) sep (join-2 (rest alist) sep))
      )
    )
    
  • 2006-03-11: Edward points out below that mapconcat could be used to simplify. E.g.:
    (mapconcat (lambda (x) x) (list "a" "b" "c" "d") ":")
    

    or even better, lose the silly lambda and use the identity function:

    (mapconcat 'identity (list "a" "b" "c" "d") ":")
    

    My point was more to see what Lisp syntax looks like for some kind of semi-useful, typical tasks rather than to actually solve this particular problem, but it is a useful little function, and the resulting code is much more readable. Thanks, Ed!

  • 2006-03-12: Faried points out that I used a non-standard closing paren style. Here’s the example with the more standard style and I’ve renamed it from join-2 to join to be consistent.
    (require 'cl)
    
    (defun join (alist sep) 
      (if (null (rest alist)) 
          (first alist) 
        (concat (first alist) sep (join (rest alist) sep))))
    

Books on Lisp from Amazon.com

The Little Schemer - 4th EditionThe Seasoned SchemerPractical Common Lisp

9 thoughts on “Feeling Lispy

  1. If you’re using Emacs Lisp (or Common Lisp) instead of Scheme, you can use first and rest instead of car and cdr. That may or may not make your code look cleaner, but it makes clear that you’re doing something with the first element of the list and the rest of the list. Another thing you could do up above is use if instead of cond, since it’s a conditional with just two branches (that’ll cut down on the number of parens).

    You’ve used (length alist) as a test to see if the list has one element or more. Another way of looking at it is “do something if the list has more elements, otherwise do something else”. You could write that as (and I hope this comes through properly):

    (defun join-2 (alist sep)
      (if (null (cdr alist))
          (car alist)
        (concat (car alist) sep (join-2 (cdr alist) sep))))
    

    One advantage of not using (length alist) is that the length function has to walk down the entire list every time you call it. It can be slow for large lists.

    Lastly, Common Lisp offers many ways to do this sort of thing. Here’s one:

    (defun join-3 (alist sep)
      (reduce #'(lambda (a b) (concat a sep b)) alist))
    

    You may need to type in (require ‘cl) for some of the above functions to work.

  2. Keep in mind almost any language is confusing and hard to mentally parse until you’re at least converstant in it. Think of C’s pointers, C++’s templates, Perl’s… well, all of Perl.

    I think the big win is less Lisp specifically, as opposed to functional programming. To that end, check out Haskell over Scheme.

  3. Yeah, I’ve noticed that all the Lisp I’ve seen typically puts all the closing parens together on one line. Why is that? It seems incredibly difficult to read that way?

  4. Just a correction: I believe Scheme’s big selling point is that it’s more functional than lisp. It doesn’t have as many side-effect-ful functions, etc… At least, that’s what I recall from my intro to CS classes which were all in Scheme…

  5. Nonny Mouse, you’re absolutely right. I’ve since realized that Scheme is a very pure, functional form of Lisp, as you can see in my later post, The Little Schemer.

Leave a Reply

Your email address will not be published. Required fields are marked *