Two posts in one, just because I can.
Rambling #1: One thing that makes a language like Ruby easy to read is that
foo.bar always has the same meaning:
In Common Lisp you don't have that. One of CL's strengths is that everything looks the same, it's all s-expressions. However a cost of this is that you can't tell just by looking whether
(foo bar) is:
- A special operator foo, that does something with bar (maybe assigning it a value, maybe conditionally evaluating it, who knows).
- A system-defined function call foo, where bar is evaluated and the result is passed in as an argument, and which you can't redefine because it's in the
- A user-defined function call foo, where bar is evaluated and the result is passed in as an argument, and which you can re-define if you want.
- A method call foo, where bar is possibly an object used to dispatch the method call (or maybe not).
- A macro foo, which can produce code that does practically anything, or nothing at all, with bar.
Along the same lines, even if you know for sure that
(foo bar baz) is a
method call, you can't tell by looking whether bar, or baz, or both, are
objects which determine how the method call is dispatched. You have to look
it up in documentation or inspect the method somehow. As far as I know.
(EDIT: Slime has lots of tools for doing this kind of inspection., as was pointed out to me by Ivar.)
This is a good feature of Common Lisp, in terms of flexibility and power; multimethods are great, the consistency and lack of syntax and precedence is great in many ways. But it can be a bad thing in terms of easy readability. It's a bit of a tradeoff.
Ruby code like
arr.reverse.sort.join(',') just flows right along. And it's highly predictable. I know
reverse is a method call, and it's defined in whatever class
arr is an instance of (or a mixin of that class, or a meta-class I guess), and it returns something that has a
sort method. And I can redefine any of those things.
Common Lisp non-working pseudo-code:
(join (sort (reverse arr)) ","). Not as much fun to read, and oops, in this case
SORT is a system-defined standard function, so if I want to write this I'll have to go with
SORT2 to avoid collisions (which you see a lot of in Lisp examples and literature).
Rambling #2: Way back in 2006, I decided to learn Common Lisp because I'd heard such nice things about it. I ended up abandoning that project very quickly (only to try again late in 2007, and stick with it this time). One reason (among many many others) that I gave up was the fact that it took me a good hour or two even to figure out how/where to download and install Common Lisp.
I remember going to a terminal and searching the Portage tree for packages matching the name "common lisp". Zero results. OK, how about just "lisp". One result I got back was
clisp, which at that time, based on the name, I assumed was "just another name for the Common Lisp interpreter". However a search of the Gentoo forums revealed that a lot of people were using SBCL, or CMUCL, or many others. And those things were "Common Lisp" too? How confusing.
Little did I know that there is no THE Common Lisp. There are a bunch of implementations of the Common Lisp standard (or varying parts of the standard). To a newbie, this is somewhat confusing. Which one should you pick? What the heck is the difference? If it's a standard, does it even matter which you pick? So before you can even start writing code, you get to treat yourself to a history lesson to learn why there are all these different implementations, and then go on a fact-finding mission to try to find an implementation that's right for you.
It wouldn't matter, if the standard was complete enough that it covered all of what you'd want to do. But the standard is old, and arguably out-of-date. And it doesn't cover things like threads, sockets, POSIX utilities, etc. that you're really likely to use. And if something isn't in the standard, you can bet good money that every implementation does that thing in its own way, or not at all. A lot of sufficiently complex code in Common Lisp will demand that you go to great lengths to get it to run on different implementations, judging by some of the library code I've read.
Consider CL-FAD. A function
DELETE-DIRECTORY-AND-FILES is used to (you guessed it) recursively delete a directory and its files. Here are the guts of that function:
(cond ((directory-pathname-p file) #+:lispworks (lw:delete-directory file) #+:cmu (multiple-value-bind (ok err-number) (unix:unix-rmdir (namestring (truename file))) (unless ok (error "Error number ~A when trying to delete ~A" err-number file))) #+:scl (multiple-value-bind (ok errno) (unix:unix-rmdir (ext:unix-namestring (truename file))) (unless ok (error "~@<Error deleting ~S: ~A~@:>" file (unix:get-unix-error-msg errno)))) #+:sbcl (sb-posix:rmdir file) #+:clisp (ext:delete-dir file) #+:openmcl (ccl:delete-directory file) #+:cormanlisp (win32:delete-directory file) #+:ecl (si:rmdir file) #+(or :abcl :digitool) (delete-file file)) (t (delete-file file)))
What's going on there? All those
#- lines are kind of like the Common Lisp equivalent of C preprocessor macros. #+ and #- hide (or include) a line of code from the view of the compiler, depending on which implementation you're using. Ugh.
So in this case, you have what is a pretty simple function (1-5 lines of code, in each implementation), but you get to have the fun of writing it TEN TIMES, all woven together like spaghetti. There are many other such compatibility layers.
Kudos to people who go to the effort of writing portable code like this, but I don't see why it should be necessary. This strikes me as a senseless and wasteful duplication of effort. Not just on the part of the people writing code to run on all these implementations, but also on the part of the people working on the implementations themselves. Why not join forces and make one big mature official Common Lisp for everyone to use? What benefit is there to having so many implementations? Does the benefit outweigh the amount of work someone has to do to write portable code to share with the community? Maybe there's a good reason for it that I'm not aware of. But it seems to me like it defeats the purpose of having a standard in the first place.
On the other hand if I want to write some Perl, I do an
emerge perl (substituting your package manager of choice) and then I have Perl. The same exact Perl (barring version differences, and slight differences between Perls on different OSes) that every other Perler in the world has. If I write a Perl script, any other person with Perl could reasonably expect to be able to run it. Same with PHP, Python, Ruby, and many other languages. (People are working on separate interpreters for Ruby, e.g. JRuby but from what I know, they're going to great lengths to make it run EXACTLY like the "official" Ruby, even down to copying bugs to maintain compatibility.)
I know now that there are things like Lisp in a Box that give you an easily-installable Common Lisp environment. Which is really nice for learning purposes. I wish I'd have found it back in 2006 (if it existed at that time). But it only delays the mess you're going to have to handle someday if you want to write portable code, or if you want to use anything other than CLISP or Allegro.
I have an urge to start porting some of my favorite Ruby gems to Common Lisp. But I am not going to install 10 CL implementations just so I can re-write my code 10 times to get it working on all the Lisps in the world. Very likely my code would be ruthlessly SBCL-specific.
(This is one reason I'm kind of hoping Arc takes off. Hopefully there would only be one Arc implementation.)