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.
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-nameis better than#'var-namebut, 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.
*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.
From Stephen C. Gilardi.