The Beauty of Lisp-1

I started working with Clojure after OOPSLA. Clojure is a Lisp-1.* I had never seriously used a Lisp-1 before Clojure. I was exposed to Scheme, but not to a great extent. I have always considered Lisp-1 to be the more elegant in theory and Lisp-2 to be the more practical. However, Clojure innovates in its approach to evaluation past the Lisp-1/Lisp-2 dichotomy and reveals serious research into the art of language design.

Lisp-1 encourages a very elegant style. A Lisp-1 has no need of flet or labels , since a let will do. Also, funcall is not needed. It arguably encourages a more functional style, since no distinction is made between the function value of a name and the non-function value of a name. The elegance and beauty lie in the consistent treatment of function and non-function values.

This elegance comes at a cost. Name collisions are more frequent in a Lisp-1. In Common Lisp (a Lisp-2), I can name a parameter list without conflicting with the function called list . Not so in Clojure. I also lose the helpful visual hints that a symbol is being treated as a function, namely #'cons and (funcall f 13) . If I am not accustomed to reading higher-level code, I might find it easier to read code that is explicit about when a value is being treated as a function. These tradeoffs are subtle and will inevitably displease some programmers.*

But Clojure has dealt with these tradeoffs beautifully. #'cons will normally evaluate to the cons function, even when cons is bound in a surrounding let . But so does cons alone when cons is not bound in a let .* Clojure is more functional in style than Common Lisp, so it makes sense to encourage treating functions like other values. Further, Clojure treats :keywords , vec 's, and map 's as functions. Having to funcall them would reduce their usefulness. Lisp-1 in Clojure works to make it the elegant language that it is.

I did not originally appreciate that Clojure is a Lisp-1. I was used to Common Lisp. But with use, I revealed its beauty. Though some tradeoffs were necessarily made, Lisp-1 was a decision consistent with its other design choices. In the end, it comes down to this: every decision point in language design is an opportunity to reiterate, refine, and uncover the beauty of the language. Clojure has only just begun to chart its way through design space. I am optimistic about Clojure and its future.

Addendum

Thanks goes to grand for correcting a mistake.

A nice article on Clojure that gives one wrong tip: to use #'var-name to access a shadowed global.

Don't do that! You'd better use namespace-where-defined-or-alias/var-name. There are several reasons not to use #'var-name:

* it doesn't work if the value of your var is not invokable,

* it evaluates to something different: it returns the var an not its value. It happens that vars proxy function calls to their values but a var can be rebound:

      (def a (map str (range 10)))
(def b (map #'str (range 10)))
(take 5 a)
("0" "1" "2" "3" "4")
(take 5 b)
("0" "1" "2" "3" "4")
(binding [str identity] (doall a))
("0" "1" "2" "3" "4" "5" "6" "7" "8" "9")
(binding [str identity] (doall b))
("0" "1" "2" "3" "4" 5 6 7 8 9)

NB: @#'var-name is better than #'var-name but, really, just use the namespaced symbol.

Thanks for the correction.

I asked on the mailing list, and Stephen C. Gilardi gave a good guide to how to deal with shadowing a value.

- Avoid using an argument name or let-bound local name that "shadows" the (unqualified) name of a var you need to use within its scope

- Failing that, use a namespace qualified symbol to refer to the var

- If namespace qualifying a symbol is too verbose, use "alias" to create a shorter name for the namespace

- If that's still too verbose, use #' or (var ...) to refer to the var in the current namespace

- #'x and (var x) are equivalent. Use whichever you find readable and adequately succinct.

Bottom line: don't use #' to bypass shadowing. Avoid shadowing if possible. If it's not possible, use the fully qualified name.

I have disabled comments. If you think this post is important enough to talk about, thanks a lot! You can always contact me at eric@lispcast.com. Otherwise, there are plenty of good places to discuss or respond to what you have read. I would prefer if you responded on your own blog. Barring that, there are newsgroups and mailing lists. Finally, Reddit, Hacker News, and other link sites. Happy hacking!

*Lisp-1: a Lisp with a single namespace for function definitions and variables. In other words, the first element of a function call s-expression is evaluated in the same way as the rest of the elements. Examples: Scheme and Clojure.

Lisp-2: a Lisp with distinct namespaces for functions and variables. The first element of a function call has different evaluation semantics from the rest of the elements. Example: Common Lisp.

*There are more costs that I did not detail here. For a complete education in the tradeoffs between Lisp-1 and Lisp-2, please see Technical Issues of Separation in Function Cells and Value Cells.

*#'x expands to (var x) , essentially a thread-local binding that bypasses let bindings. See Special Forms.

Update: don't use #'x to refer to global values. See my addendum below.

From cgrand.