Friday, September 24, 2004
I have finally been getting some good Lisp hacking in. I feel like I have climbed my way out of total "newbieness" and I'm in beginner mode now. My hacking is still not very smooth: lots of trips back to CLHS or CLtL to look up function details, or even to try to find out whether there is a standard function that does what I need. There is nothing like writing a really nice Lisp function for something, only to discover 10 minutes later that Lisp hackers 20 years ago thought of the same thing and provided it in the standard library.
Coming from a C/C++/Java background, here are the things that really strike me about Lisp:
- The parenthesis are really no big deal. I don't know how many years I stayed away from Lisp simply because I was intimidated by the parenthesis. Yes, you notice them for the first week or so, then they gradually fade out of your conciousness and you just go with it. Emacs is a huge win here. If you're reading this and you are staying away from Lisp because you heard all the old jokes about "LISP" standing for "lots of irritating silly parenthesis," you'll have to find a new reason. This one doesn't cut it anymore.
- It is really cool that everything is a function. It's nice being able to plop an IF form in the middle of a complex expression to select between a couple of choices. It's interesting to note that C developed the conditional operator ("?:") to reach the same level of convenience because C makes the distinction between statements and expressions. With Lisp, you don't have to do that, and you get things like IF, WHEN, UNLESS, DO, etc., all returning useful values.
- High-order functions are a BLAST! You can get some effect of high-order functions by passing around pointers to functions in C, but it just isn't done very often and you still don't get closures. You really don't realize how useful this is until you try it. Basically, it allows you to start writing very generic algorithms and parameterizing parts of their function in the same way you're used to parameterizing with data values in C functions. This can be as simple and mundane as a :KEY function parameter provided to extract a data value from a structure passed to the POSITION function, or as complex as you want with something passed to a function like MAPCAR. The ability to use high-order functions eliminates so much redundant code, it's amazing.
- Going along with high-order functions, it's really nice to have anonymous functions. High-order functions would be used a lot less, I suspect, if you had to name every function. It's interesting that this is exactly what C forces you to do to use a function pointer. Because functions are therefore more heavyweight, it's harder to justify using the function-call-by-pointer mechanisms in C programs. In contrast, Lisp makes this trivial. The only downside I have found with LAMBDA functions is that it's sometimes hard to debug a stack trace if you have lots of anonymous functions being used. The debugger output isn't too meaningful.
- Going along with high-order functions and anonymous functions, closures just rock! The ability to reach out and grab a free variable that is in scope is just cool. It allows you to easily squirrel something away for later without having to pass it into a LAMBDA function as a formal parameter or create any storage for it explicitly. As a result, code is a lot shorter and less complex than it would be if you had to manage all that yourself.
- Lisp macros are simply mind-blowing. When you realize how underpowered C macros are, you'll never want to go back. Being able to write code at compile time is just cool. The fact that it allows you to actually add language syntax is even better. And, oh, by the way, Lisp macros wouldn't be nearly as powerful without that Lisp code = list data thing that is assisted by all those parenthesis.
- One thing I still can't seem to get my head around is prefix notation for arithmetic expressions. I have no problem with prefix notation for general function calls, but I seem to crave infix for arithmetic expressions. This is particularly odd since my dad worked at HP since I was a little kid and I grew up on RPN calculators. You would think that prefix would be no big deal, but it seems to be 180 degress from RPN and that somehow screws me up. I find that when I'm writing a big arithmetic expression in Lisp that I'm constantly jumping back to the front of the expression to add in another operator that I somehow left out. I suppose that's because I'm still thinking in RPN. Everything that would come at the end of an RPN expression instead comes first in Lisp. Over time, I'm expecting this to get better.
I'm sure I'm missing a few other things, but that's a good start.
Update: Something I totally forgot about at the time I originally wrote this but which is definitely unique in programming languages: symbols. Yes, you're tempted to think of symbols simply as variables, as with other languages, but they're far more than that. Being able to use symbolic representations for various abstractions is very convenient, both when originally doing development, as well as debugging.
With the prefix arithmetic operator thing, I kind of have that issue too. In my experience it points to me not understanding something as well as I should. In my case, I probably have a mechanical understanding of +-*/ lodged in my brain, sufficient to calculate things quickly.
By the metrics of (idea thing thing) or
(idea thing thing)
that probably means I don't have an "idea" of arithmetic, merely some mechanical substitute.
One thing, which makes higher order functions that usefull is that in Lisp you don't need to specify the types of the arguments and the return value of a function. That's why in C one 'mapcar' function wan't do: you'll have to have different functions for int (*f)(int), int (*f)(float), float (*f)(int), etc.
indentation may help to make arithmetic expressions more easy do
understand. another thing, you may have to think differently in you
head. for example, instead of saying in your head "a multiplied to b"
you may want to think something like "multiply a to b" or "the sum of
x and y". it may sound silly but it really helps!
Another nice things about closures is that I find I use them where in other languages I'd have to make a small class to just hold the data, make a constructor to pass the parameters to, etc. In Lisp (Ruby, Groovy, etc.) those annoying little classes just go away and you can just focus on your problem.
(defmacro rpn (&rest args)
(cons 'progn (mapcar #'switch-from-rpn args)))
(defun switch-from-rpn (args)
(if (atom args)
(append (last args)
(subseq args 0
(- (length args)
(rpn ((1 3 +) 2 +)
(3 2 1 *)
('a 'b 'c list))
It doesn't handle any reader macros like ' or #' but it's still useful, if you like that sort of thing.
Infix reader-macro by Mark Kantrowitz?
http://www.cliki.net/infix sourceFrom the source
;;; Begin the reader macro with #I( and end it with ). For example,
;;; #I( x^^2 + y^^2 )
;;; is equivalent to the Lisp form
;;; (+ (expt x 2) (expt y 2))
;;; but much easier to read according to some folks.
;;; If you want to see the expansion, type a quote before the #I form
;;; at the Lisp prompt:
;;; > '#I(if x<y<=z then f(x)=x^^2+y^^2 else f(x)=x^^2-y^^2)
;;; (IF (AND (< X Y) (<= Y Z))
;;; (SETF (F X) (+ (EXPT X 2) (EXPT Y 2)))
;;; (SETF (F X) (- (EXPT X 2) (EXPT Y 2))))
Re "multiply a by b": how does that work with non-commutative operations? (- 7 2) is definitely not "subtract 7 from 2" (not in Emacs Lisp, anyway); "the difference of 7 and 2" has some possibilities but is not terribly convincing. And exponents???
Bob, of "One Idiot's Blog"
Post a Comment
Links to this post: