Saturday, December 08, 2007

Thoughts on Lisp: Things other languages took from Lisp

Lisp has a lot of features that I really like. A number of them have crossed over into modern mainstream languages, e.g. Java, Perl, Python, Ruby, and JavaScript. Some of these features definitely appeared first in Lisp. John McCarthy's History of Lisp paper captures several of those. Other features may have been adopted by Lisp from other languages, but certainly Lisp had them before any other language currently in wide use.

Here are several features of Lisp which I really like, and which have crossed over into modern, mainstream programming languages:

  • Conditional expressions. (Not to be confused with conditional statements!) McCarthy invented these for Lisp, but now they've spread to every language. For C developers, these are expressions using the

    (test ? consequent: alternate)

    form. For Lisp developers, it's just anything using IF.

  • Garbage collection. The concept of garbage collection preceded Lisp, but was implemented in individual application. Lisp was the first programming language to support GC directly. Nowadays, every mainstream language newer than C++ has GC too. And I hear that even the C++ community has been talking about ways to fit it into the language someday.

  • Bounds-checked arrays. I don't know where this actually appeared first. I do know that Lisp has had it for a very long time. All the post-C++ mainstream languages have this now, too.

  • Interactive development via the "Read-Eval-Print Loop" (REPL). These days, developers using Python, Ruby, and even Java (via Eclipse) enjoy the benefits of interactive development.

  • Lexical scope by default (Scheme and Common Lisp, not Emacs Lisp.) Lexical scope makes possible for the reader of a program's text to figure out where each value comes from. This is an immense aid to human readability, and to static introspection such that as performed by IDEs. Most modern languages use lexical scoping most of the time, although there are certainly ways to use other scopes in, e.g., Perl.

  • Closures. When what you really want is a callback, a closure is sometimes far more elegant than, say, an object implementing a single method. Closures have crossed over, but the depth of support varies considerably. Perl and Ruby have full closure support. Python lets a closure refer to, but not modify, a lexically-closed-over value. As Dan Weinreb noted just today, Java only has anonymous objects, which are weak substitutes for real closures because they can only refer to 'final' lexically-closed-over values.

I'm sure I've missed more positive features that have crossed over from Lisp to modern languages. Please mention those in the comments. If you know (more of) the history of any particular feature --- and in particular, if you can provide references --- please add a comment as well!

One more assumption

Extending the list of my assumptions and prejudices (perhaps I should have called them values?) in this post, here's one more::

  1. Encapsulation is good. It should be exceedingly difficult, if not downright impossible, to reach into the guts of a complex abstraction and fiddle with it.

On typing (poh-tay-toh) and typing (poh-tah-toh)

One often-heard statement about Lisp is that it requires much less code (that is, less typing) to get something done than Java. I wonder what percentage of that is because everything is done with one type: the pair. More typing requires more typing, as it were.

Does this fit with people's practical experience? How does it fit with other comparison points, e.g. Python?

Coming up next...

Things I like in Lisp that still haven't crossed over


Daniel said...

It's not that there's only one type (the pair); there are plenty of types. It's mainly that you don't have to put in type declarations. Also, by encouraging expression-oriented programming (to put it very crudely, don't create local variables for every intermediate quantity). And less is done with reserved keywords; Lisp uses structure instead, in most places. Also, some things can be expressed much more clearly using higher-order functions (e.g. mapping a little function over a list).

Otherwise, I'm not sure Java is really all that much more succinct.
This is based on my scanning quickly over some Java code, not a careful study.

Jeremy said...

I was a little hyperbolic. There are lots of types in Lisp. However, Scheme only gives you two composite types --- cons, and vector. CL also gives you hash tables, and the ability to make structures and structured objects. The typing (and typing) overhead of structures or objects is way higher than just consing up a list and going to town. The privileged syntax for lists makes it a lot shorter to write `(1 2 ,(+ a b)) than to write (make-instance :x 1 :y 2 :z (+ a b))

I certainly didn't mean to suggest that Java code would be more succinct under any circumstances!

GresP said...

Interesting point about lexical scope. It doesn't get mentioned much these days. I was looking through the code of the html-helper code for emacs and surely enough it was driving me nuts trying to figure out where all the variables were coming from!

Of course the real benefits come when trying to implement something more complex, particularly something concurrent.