Why I love Lisp

by

This post was extracted from a small talk I gave at Simplificator, where I work, titled “Why I love Smalltalk and Lisp”. There’s another post titled “Why I love Smalltalk” published before this one.

Desert by Guilherme Jófili

Lisp is an old language. Very old. Today there are many Lisps and no single language is called Lisp today. Actually, there are as many Lisps as Lisp programmers. That’s because you become a Lisp programmer when you go alone in the desert and write an interpreter for your flavor of lisp with a stick on the sand.

There are two main Lisps these days: Common Lisp and Scheme, both standards with many implementations. The various Common Lisps are more or less the same, the various Schemes are the same at the basic level but then they differ, sometimes quite significantly. They are both interesting but I personally failed to make a practical use of any of those. Both bother me in different ways, and of all the other Lisps, my favorite is Clojure. I’m not going to dig into that, it’s a personal matter and it’ll take me a long time.

Clojure, like any other Lisp, has a REPL (Read Eval Print Loop) where we can write code and get it to run immediately. For example:

5
;=> 5

"Hello world"
;=> "Hello world"

Normally you get a prompt, like user>, but here I’m using the joyful Clojure example code convention. You can give this REPL thing a try and run any code from this post in Try Clojure.

We can call a function like this:

(println "Hello World")
; Hello World
;=> nil

It printed “Hello World” and returned nil. I know the parenthesis look misplaced but there’s a reason for that and you’ll notice it’s not that different from Javaish snippet:

println("Hello World")

except that Clojure uses the parenthesis in that way for all operations:

(+ 1 2)
;=> 3

In Clojure we also have vectors:

[1 2 3 4]
;=> [1 2 3 4]

symbols:

'symbol
;=> symbol

The reason for the quote is that symbols are treated as variables. Without the quote, Clojure would try to find its value. Same for lists:

'(li st)
;=> (li st)

and nested lists

'(l (i s) t)
;=> (l (i s) t)

Here’s how defining a variable and using it looks like

(def hello-world "Hello world")
;=> #'user/hello-world

hello-world
;=> "Hello world"

I’m going very fast, skipping lots of details and maybe some things are not totally correct. Bear with me, I want to get to the good stuff.

In Clojure you create functions like this:

(fn [n] (* n 2))
;=> #<user$eval1$fn__2 user$eval1$fn__2@175bc6c8>

That ugly long thing is how a compiled function is printed out. Don’t worry, it’s not something you see often. That’s a function, as created by the operator fn, of one argument, called n, that multiplies the argument by two and returns the result. In Clojure as in all Lisps, the value of the last expression of a function is returned.

If you look at how a function is called:

(println "Hello World")

you’ll notice the pattern is, open parens, function, arguments, close parens. Or saying it in another way, a list where the first item is the operator and the rest are the arguments.

Let’s call that function:

((fn [n] (* n 2)) 10)
;=> 20

What I’m doing there is defining an anonymous function and applying it immediately. Let’s give that function a name:

(def twice (fn [n] (* n 2)))
;=> #'user/twice

and then we can apply it by name:

(twice 32)
;=> 64

As you can see, functions are stored in variables like any other piece of data. Since that’s something that’s done very often, there’s a shortcut:

(defn twice [n] (* 2 n))
;=> #'user/twice

(twice 32)
;=> 64

Let’s make the function have a maximum of 100 by using an if:

(defn twice [n] (if (> n 50) 100 (* n 2))))

The if operator has three arguments, the predicate, the expression to evaluate when the predicate is true and the one when it’s false. Maybe like this it’s easier to read:

(defn twice [n]
  (if (> n 50)
    100
    (* n 2)))

Enough basic stuff, let’s move to the fun stuff.

Let’s say you want to write Lisp backwards. The operator at the last position, like this:

(4 5 +)

Let’s call this language Psil (that’s Lisp backwards… I’m so smart). Obviously if you just try to run that it won’t work:

(4 5 +)
;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

That’s Clojure telling you that 4 is not a function (an object implementing the interface clojure.lang.IFn).

It’s easy enough to write a function that converts from Psil to Lisp:

(defn psil [exp]
  (reverse exp))

The problem is that when I try to use it, like this:

(psil (4 5 +))
;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)

I obviously get an error, because before psil is called, Clojure tries to evaluate the argument, that is, (4 5 +) and that fails. We can call it explicitly turning the argument into a list, like this:

(psil '(4 5 +))
;=> (+ 5 4)

but that didn’t evaluate it, it just reversed it. Evaluating it is not that hard though:

(eval (psil '(4 5 +)))
;=> 9

You can start to see the power of Lisp. The fact that the code is just a bunch of nested lists allows you to easily generate running programs out of pieces of data.

If you don’t see it, just try doing it in your favorite language. Start with an array containing two numbers and a plus and end up with the result of adding them. You probably end up concatenating strings or doing other nasty stuff.

This way of programming is so common on Lisp that it was abstracted away in a reusable thing call macros. Macros are functions that receive the unevaluated arguments and the result is then evaluated as Lisp.

Let’s turn psil into a macro:

(defmacro psil [exp]
  (reverse exp))

The only difference is that I’m now calling defmacro instead of defn. This is quite remarkable:

(psil (4 5 +))
;=> 9

Note how the argument is not valid Clojure yet I didn’t get any error. That’s because it’s not evaluated until psil processes it. The psil macro is getting the argument as data. When you hear people say that in Lisp code is data, this is what they are talking about. It’s data you can manipulate to generate other programs. This is what allows you to invent your own programming language on top of Lisp and have any semantics you need.

There’s an operator on Clojure called macroexpand which makes a macro skip the evaluation part so you can see what’s the code that’s going to be evaluated:

(macroexpand '(psil (4 5 +)))
;=> (+ 5 4)

You can think of a macro as a function that runs at compile time. The truth is, in Lisp, compile time and run time are all mixed and you are constantly switching between the two. We can make our psil macro very verbose to see what’s going on, but before, I have to show you do.

do is a very simple operator, it takes a list of expressions and runs them one after the other but they are all grouped into one single expression that you can pass around, for example:

(do (println "Hello") (println "world"))
; Hello
; world
;=> nil

With do, we can make the macro return more than one expression and to make it verbose:

(defmacro psil [exp]
  (println "compile time")
  `(do (println "run time")
       ~(reverse exp)))

That new macro prints “compile time” and returns a do that prints
“run time” and runs exp backwards. The back-tick, ` is like the quote ' except that allows you to unquote inside it by using the tilde, ~. Don’t worry if you don’t understand that yet, let’s just run it:

(psil (4 5 +))
; compile time
; run time
;=>; 9

As expected, compile time happens before runtime. If we use macroexpand things will get more clear:

(macroexpand '(psil (4 5 +)))
; compile time
;=> (do (clojure.core/println "run time") (+ 5 4))

You can see that the compile phase already happened and we got an expression that will print “run time” and then evaluate (+ 5 4). It also expanded println into its full form, clojure.core/println, but you can ignore that. When that code is evaluated at run time.

The result of the macro is essentially:

(do (println "run time")
    (+ 5 4))

and in the macro it was written like this:

`(do (println "run time")
     ~(reverse exp))

The back-tick essentially created a kind of template where the tilde marked parts for evaluating ((reverse exp)) while the rest was left at is.

There are even more surprises behind macros, but for now, it’s enough hocus pocus.

The power of this technique may not be totally apparent yet. Following my Why I love Smalltalk post, let’s imagine that Clojure didn’t come with an if, only cond. It’s not the best example in this case, but it’s simple enough.

cond is like a switch or case in other languages:

(cond (= x 0) "It's zero"
      (= x 1) "It's one"
      :else "It's something else")

Around cond we can create a function my-if straightforward enough:

(defn my-if [predicate if-true if-false]
  (cond predicate if-true
        :else if-false))

and at first it seems to work:

(my-if (= 0 0) "equals" "not-equals")
;=> "equals"
(my-if (= 0 1) "equals" "not-equals")
;=> "not-equals"

but there’s a problem. Can you spot it? my-if is evaluating all its arguments, so if we do something like this, the result is not as expected:

(my-if (= 0 0) (println "equals") (println "not-equals"))
; equals
; not-equals
;=> nil

Converting my-if into a macro:

(defmacro my-if [predicate if-true if-false]
  `(cond ~predicate ~if-true
         :else ~if-false))

solves the problem:

(my-if (= 0 0) (println "equals") (println "not-equals"))
; equals
;=> nil

This is just a glimpse into the power of macros. One very interesting case was when object oriented programming was invented (Lisp is older than that) and Lisp programmers wanted to use it.

C programmers had to invent new languages, C++ and Objective C, with their compilers. Lisp programmers created a bunch of macros, like defclass, defmethod, etc. All thanks to macros. Revolutions, in Lisp, tend to just be evolutions.

Thanks to Gonzalo Fernández, Alessandro Di Maria, Vladimir Filipović for reading drafts of this.

Croation translation on http://science.webhostinggeeks.com/zasto-volim-lisp


30 responses to “Why I love Lisp”

  1. Paul Makepeace (@paulmakepeace) Avatar

    Great little introduction, Pablo. I’ve often wondered what Lisp macros were all about but never persisted with the Lisp books. Now I have an insight, thanks.

  2. drKreso Avatar

    Great introduction. You have a typo on line 4 – one extra parentheses

    1 (defn twice [n]
    2 (if (> n 50)
    3 100
    4 (* n 2))))

    1. Pablo Avatar

      Fixed. Thanks!

  3. Michael Avatar
    Michael

    There are several occurences of “it’s” where you meant “its”.

    1. Pablo Avatar

      Thanks, I corrected a few, if there are more, please, let me know.

  4. Sasha Avatar

    I’ve played with Scheme a bit while working my way through the SCIP. Although I’ve loved the mind-bending elegance of Lisp I couldn’t get to terms with all those parentheses. I guess, that’s the price you pay when you learn to program by reading the C Programming Language, 2nd Ed.

    1. Pablo Avatar

      You stop being bothered by the parens after a while, like it happens with most syntax artifacts. Something to like about Clojure is that it has more syntax than other Lisps, so there are less parens and more of other things, like vectors: [1 2 3], symbols: :first, maps: {:name "Pablo", :blog "pupeno.com"}, sets: #{:apple, :banana, :microsoft}. I think it deserves a try.

      1. Gary Trakhman Avatar
        Gary Trakhman

        by symbols :first you mean keywords

      2. Pablo Avatar

        Yeah, my mistake, keywords.

      3. Gary Trakhman (@gtrakGT) Avatar

        by symbols: :first, you mean keywords: :first

    2. Jambro Avatar
      Jambro

      I couldn’t agree with you more!

      (Same background, too!)

    3. Bob Avatar
      Bob

      I just wanted to comment on the parenthesis thing. I know it’s not syntactically beautiful but if you know why the designers of Lisp chose parenthesis then it may make more sense and perhaps you see it’s elegance. I’ll give it shot. If you ever tried designing a compiler or interpreter you’ll find that the more complex you make the syntax of your language the more code you’ll need to parse that syntax. By using parenthesis and putting operators or function names at the beginning you can reduce the complexity and behind the scenes code involved for parsing. This helps to speed up parsing. Parenthesis are an efficient way to express the beginning and end of a segment of code, just like in math formulas. In compiled languages there is no need to resort to these tricks so they syntactically more elegant.

  5. Aaron Godin Avatar

    Thanks for the post. I’ve been meaning to learn a lisp for at least a year now, and this is just what I needed. Really interesting stuff!

  6. geek42 Avatar

    there is another cool , also old, language which use another op data order

    its Forth :]

    1 2 +
    3

  7. Gary Rowe Avatar

    Great introduction. Good to see a simple, progressive article that illustrates why Lisp dialects are so powerful and yet so concise.

  8. Anon Avatar
    Anon

    > If you don’t see it, just try doing it in your favorite language.

    Ok, here goes, this is for Tcl 8.5:

    $ tclsh
    % namespace path { ::tcl::mathop }
    % eval [ lreverse [ list 4 5 + ] ]
    9

  9. Ian Bowman (@itISiBOWMAN) Avatar

    As others stated, I certainly enjoyed this post. Thanks!

    Have you experimented integrating Clojure with Processing by any chance?

    1. Pablo Avatar

      Thank you for the comment and to your question, no, I haven’t.

  10. Ramon Leon Avatar

    OK, now I’m waiting for the Smalltalk vs Lisp post; if you love them both, contrast them.

  11. Eduardo Avatar
    Eduardo

    Hello, I’m new at Clojure, but when I try this snippet on REPL…

    (cond (= x 0) “It’s zero”
    (= x 1) “It’s one”
    :else “It’s something else”)

    ….it stays forever doing nothing. What’s wrong with this?

    1. havvy Avatar

      You don’t get an error saying the symbol x doesn’t exist?

    2. Pablo Avatar

      Are those proper double quotes, the ones that are vertical and straight or are those the nice ones that are slanted and confuse compilers? It sounds to me like you have an open quote or parens somewhere which hasn’t been closed yet.

  12. Jambro Avatar
    Jambro

    I remember learning Lisp for an AI course back in college. I wondered “What sadistic misanthrope invented that abhorrent language?” I am impressed with the things you can do so very easily in lisp, but just hated “thinking in lisp.” It hurt my head!

  13. Greg Baryza Avatar
    Greg Baryza

    Clojure seems to be getting all the press these days, but I’d like a LISP “expert” to rate it against Armed Bear Common Lisp which runs on the JVM as well.

  14. RC Roeder Avatar
    RC Roeder

    Spend many years prgramming in LISP, AutoLisp to be exact. Love/hate thing, there were things you could do no other language could do, such as creaing a function, in runtime, the passing that function as a argument into another function to be executed. Alos the free form data stuctures that gives C++ programers nighmares.

  15. lsatensteinLesli Avatar

    I think I will stick to APL language (a variant is called J). Much easier to understand and as rich in function as is lisp.

  16. kdm999 Avatar
    kdm999

    The best explanation of macro’s I’ve ever come across….thank you!

  17. Brad Avatar
    Brad

    Pablo, if you like Lisp and Clojure *and* you are a Mac OS X fan (like to program native for the Mac or for iOS devices–or don’t like Objective-C and want a Lisp variant), you should definitely checkout my friend Tim’s language: Nu. http://programming.nu and http://programming.nu/faq

  18. Ramesh Avatar
    Ramesh

    I have a lisp file. but its not working. i want draw some circles(holes) on a line(gauge line) plz rectify my lisp or give lisp for me

    my lisp code:
    (defun c:hh ( / rows cols rowd rowv rowh rowo hole )
    (setq
    cols (getint “\nEnter the number of columns (|||) : “)
    rowd (getdist (strcat “\nGauge Line : “))
    rowo (getdist (strcat “\n1st Hole: “))
    rowh (getdist (strcat “\n2nd Hole / center : “))
    rowb (getdist (strcat “\n3rd Hole / center : “))

    hole (getdist (strcat “\nHole diameter ” (rtos 17.5) “>: “))
    )
    (if (= rows nil)(setq rows 0))
    (if (= cols nil)(setq cols 1))
    (if (= rowd nil)(setq rowd 3))
    (if (= rowh nil)(setq rowh 1))
    (if (= rowb nil)(setq rowb 1))
    (if (= rowo nil)(setq rowo 25))
    (if (= hole nil)(setq hole 17.5))
    (setq ins (getpoint “\nSelect insertion point: “))
    (setq ins (polar ins 0 rowo))
    (repeat cols

    (setq lstpnt (polar ins (* pi 1.5) rowd))
    (entmake (list (cons 0 “circle”)(cons 10 lstpnt)(cons 40 (/ hole 2.0))))

    (repeat (1- rows)
    (setq lstpntt (polar lstpnt (* pi 1.5) rowh))
    (entmake (list (cons 0 “circle”)(cons 10 2ndpnt)(cons 40 (/ hole 2.0))))

    (setq lstpnt (polar 2ndpnt (* pi 1.5) rowb))
    (entmake (list (cons 0 “circle”)(cons 10 3rdpnt)(cons 40 (/ hole 2.0))))
    )
    (setq ins (polar ins 0 rowh))
    )
    (princ)
    )

  19. HenkPoley (@HenkPoley) Avatar

    Has a ) too many –> (defn twice [n] (if (> n 50) 100 (* n 2))))

Leave a Reply

You may also like:

If you want to work with me or hire me? Contact me

You can follow me or connect with me:

Or get new content delivered directly to your inbox.

Join 5,043 other subscribers

I wrote a book:

Stack of copies of How to Hire and Manage Remote Teams

How to Hire and Manage Remote Teams, where I distill all the techniques I've been using to build and manage distributed teams for the past 10 years.

I write about:

announcement blogging book book review book reviews books building Sano Business C# Clojure ClojureScript Common Lisp database Debian Esperanto Git ham radio history idea Java Kubuntu Lisp management Non-Fiction OpenID programming Python Radio Society of Great Britain Rails rant re-frame release Ruby Ruby on Rails Sano science science fiction security self-help Star Trek technology Ubuntu web Windows WordPress

I've been writing for a while:

Mastodon

%d bloggers like this: