I've done a couple of projects in Common Lisp recently. Here are two things that I have found to be pretty useful. Note: I'm purposefully leaving out the Common Lisp Object System and macros. These are extremely cool and powerful features, but require quite a bit of time to truly appreciate. We'll start with a couple of easy ones. =)
First, I love having the ability to return multiple values. What is so great about multiple return values? It either saves you from having to write the same function multiple times OR having to use a combination of additional parameters or globals (OR both OR you having to compute the remainder, etc). Let's use a really easy example... a rounding function. There are essentially two items of interest when rounding a number, the integral portion and the truncated remainder. A solution that returns both values in C or Java ends up being a bit convoluted... I'm sure that you guys can come up with all these variations easily enough on scratch paper.
The method in which CL implements this language feature seems very well thought out.
Code:
CL-USER> (setf integral (round 25.125)) ;a function that returns multiple values operates as expected during the normal case...
25 ;(ie when I only want one value)
CL-USER> (setf remainder (nth-value 1 (round 25.125))) ;or we can get at just the remainder if that is all we want
0.125
CL-USER> (multiple-value-bind (integral remainder) ;or BOTH, with a _single_ call when we really need both of them...
(round 25.125)
(format t "integral: ~a remainder: ~d sum: ~d~%" integral remainder (+ integral remainder)))
integral: 25 remainder: 0.125 sum: 25.125
NIL
CL-USER>
I LOVE IT...
1) A single function. No code duplication.
2) A single function call; No need to make n calls for n different values.
3) No need to create global vars for multiple values or things like error conditions.
4) Simple function definitions -- round(val &integral &remainder) is a bit fugly, no?
A couple of places that I've found it to be handy are when computing some basic statistical values like average, median, std dev, var, etc. With multiple return values, I only have to make one pass over the data. Plus, I don't have to write eight different functions (or one giant fugly one).
Another example is in the CL library for hash tables. Why does Java, for example, have containsKey() and get() when get() could suffice for both functions by returning null? Well, if I need to null as a value, then I can't tell them whether I have a key with a null value or that key isn't in the hash table, hence containsKey(). The CL equivalent of get() is gethash. Instead of having a containsKey() function, you just check the second value returned by gethash. How either language does the check is an interesting problem to think about... =)
The second thing, I'm not so sure that I like it as much as I'm starting to think it is pretty useful. As with many newer languages (eg Python, Java, C#), CL has a data type hierarchy. Floats and Integers are both of type Number, etc. At the top of the hierarchy is T, which is the normal method for stating true (and like C, any values that is not nil is true). This struck me as odd at first -- I kind of like the way Object is the top in Java. Here is the twist. Nil is not just false. It is also the empty list. Keep in mind that Lisp stores is written as a series of lists (ie (+ 1 2) is the list starts with the function + and has two args). Having nil be an empty list just didn't seem like the "right thing" to do, but it does have a few unexpected practical perks.
In Java, I would normally store the results of a Monte-Carlo sim (or some other task) in a hash table using an ArrayList to hold the values. The code would almost always end up looking something like...
Code:
if (my-map.containsKey(key) == false) //or !containsKey() if you prefer... ;)
my-map.put(key, new ArrayList());
values = my-map.get(key);
values.add(newValue);
my-map.put(key, values);
There is nothing particularly unsightly about it -- 5 lines of code -- pretty standard stuff. It just always had that extra little if-step and I never really thought anything of it until I did the same thing in CL.
Code:
(setf values (gethash k ht)) ;get the list
(push v values) ;add the new value v to it; works because nil is an empty list
(setf (gethash k ht) values) ;put it back in the ht... notice that using setf on a get function
;adds an item in CL... this appears throughout the language
;;I can even reduce it to a single line of code...
(setf (gethash key ht) (push new-value (gethash key ht)))
Alright... if someone else doesn't post an example, I'm going to post some more Lisp stuff.
