A few weeks ago, I was talking to some people about the nerdy things I work on, and the nerdy things I like. The conversation inevitably turned to Emacs and Lisp, and someone asked why I liked them so much.
There are a lot of reasons, but I think the main one is that it’s such a pioneering and influential language, and most people aren’t aware of it. An astonishing number of language features we take for granted owe their existence to Lisp.
(cond
((= var "foo")
(message "This is the foo block."))
((= var "bar")
(message "This is the bar block."))
(t
(message "Default case")))
This is equivalent to the following code in C:
switch (var) {
case "foo":
printf("foo block.");
break;
case "bar":
printf("bar block.");
break;
default:
printf("Default case")
}
As you can see, they both have advantages and disadvantages:
(cond).(cond (or "foo" "bar"))switch(true) to test more complex expressions in your cases. While this works, it feels pretty hacky.x = 5, and you create a closure inside it, you also can see that x = 5. If your closure changes that to x = 50, that is invisible to your parent closure, but visible (in the same way) to any closures that closure creates. Essentially, it’s a private copy-on-write namespace. Closures were first implemented in Scheme, a Lisp-descended language. Before that, the let expressions gave some of the flexibility of closures.
(defun print-foo ()
(message foo))
(setq foo "Foo")
;; Calling (print-foo) will print "Foo".
;; Inside the let block, 'foo is changed to "bar".
;; Evaluating this prints "Bar".
(let ((foo "Bar"))
(print-foo))
;; This prints "Foo" again
(print-foo)
fset, you can change any function. Common Lisp adds flet, which works the same as let, but on functions. So you can change how library code works at an extremely deep level, but keep those changes out of the way of other code which expects the stock behavior.'((item-one . (foo bar )) (item-two . (baz quux)))
This is equavalent to
{
"item-one": ["foo" "bar"],
"item-two": ["baz" "quux"]
}
In other words, your data is expressed in the same literal notation as if it was in your code.
0 || 99;
In C, this will return TRUE; The zero is considered false, and non-zero is true. Since the condition is true, true is returned.
(or nil 99)
The same test in lisp. Rather than return t (Lisp’s equivalent to C’s TRUE), this expression returns 99. The value 99 is what caused the expression to terminate, so it’s value is returned.
It’s hard to understand the power of this at first, but consider the case where you want to assign the variable x the value of variable a if it is not false, or b otherwise. In C, you have:
if (a) {
x = a;
} else {
x = b;
}
Not very elegant. You can use the ternary syntax, where it becomes:
x = a ? a : b;
This is better, but note how you have to repeat the a variable twice. Contrast this with the lisp version:
(setq x (or a b))
No repetition. While it’s actually longer than the C version in this simple example, you gain much benefit when you use more complex expressions, such as ones where you want to assign the return value of a function if it’s true. In C, you’re stuck with calling the method twice, or assigning it’s output to a variable before the test.
In Python, you’d use:
x = a or b
;; This sets 'ex to a two-element list: (foo bar)
(setq ex '(bar baz))
;; This creates a new list 'ey; the first cell is 'foo, and the rest of the
;; list is the contents of 'ex.
;; Evaluating 'ey will show: (foo bar baz)
(setq ey (cons 'foo ex))
;; This sets the first element of 'ex to quux
(setcar ex 'quux)
;; Evaluating ex now produces: (quux bar)
;; Evaluating ey produces: (foo quux bar)
This works because ey’s cdr points to ex; it hasn’t copied ex, it’s incorporated it by reference.
So there you have a short tour of what’s cool about Lisp, and the features you’re using that you owe to it. Not all of these features were in the earliest version of Lisp, but it’s quite a feat that so many advanced high-level language features first appeared there. The original paper on Lisp is still quite relevant, and shows some of the algorithms you can implement with the earliest parts of the language.
I don’t mean to bash on C here; I actually really like C for what it is. And it’s not unlike Lisp in that it’s a highly influential language with an extremely simple core.
What’s fascinating to me about Lisp is that a language which is fifty years old is was so advanced, and is still so influential. In the fast-moving world of computing, that’s truly an amazing accomplishment.
Discussion