After slogging through a bunch of C code today, I was once again just painfully aware about something I hate about the whole algol syntax family. It's the comma separated argument lists. Mind you, I'm very familiar, even intimate with this C program. I know it very well, I architected most of it. Have been honing, refactoring, improving it for quite a while now. Even with that level of comfort, I just hate remembering the order of multi argument functions. I always have to go look them up. Of course, it's file based. Neither VIM nor Emacs do anything to help me remember the order of the arguments. They'll help me complete the function name. And static typing doesn't help when most of the arguments are int32_t's anyway (btw, if "dynamic" typing is "duck typing", what do we call "structural" typing?).
So yeah, I really really really love Smalltalk keyword syntax. It's one of many reasons that I like objective-C bettern than C++. Lisp is the only other language I know of that does keyword like syntax (I'm sure there are others, I just don't know them).
It got me curious. I have a buddy who I chat with frequently who used to be a regular Smalltalker. He does a lot of Ruby now days, though still plays with Smalltalk a bit too. I asked him if he missed it; he admitted yes, but that he often uses inline hash's (Dictionaries) to get something akin to keyword syntax. For example:
self.addAnnoyance(:name => 'Lack of Keywords', :reason -> 'Comma List Arguments Suck');
Basically, it builds an inline dictionary, and then the addAnnoyance() method queries them out. One advantage to this even over the Smalltalk equivalent is that the order of the keywords can be arbitrary (unless you do something like this).
It got me asking what price is paid to do something like that. So we did some examples. We made a class and put a computeArea() method for it which takes left, right, top, and bottom arguments. Two actually: one which has the classic ordered comment list, and one which uses the hash. Then we timed the through 1e6 repititions. It takes about 1.8 seconds on my dual 3.2 Xeon for the fast version, and 4.7 for the hash based one. So it's about a 3.5x speed hit.
For grins, we thought we'd do the same in Smalltalk (VisualWorks). Smalltalk doesn't do inline hashes. But it can do inline literal arrays. We can turn that into a dictionary using a method like Dictionary withKeysAndValues:. Adding an asArgs helper to SequencableCollection. Now we can right code like:
TitForTat computeArea1: #(#left 1 #bottom 7 #right 20 #top 2) asArgs
with an implementation that looks like:
computeArea1: aDictionary
^((aDictionary at: #right) - (aDictionary at: #left))
* ((aDictionary at: #bottom) - (aDictionary at: #top))
Running that, I actually wondered if just using the array itself wouldn't be better. Until you hit a size of about 7, you're often better just doing the linear search instead of the hash overhead. So that led to:
computeArea4: anArray
^((anArray after: #right) - (anArray after: #left))
* ((anArray after: #bottom) - (anArray after: #top))
Then of course we do the traditional Smalltalk 4 keyword argument form, time it all, and we can tabulate the results:is. Time it all:
Approach
|
Time (milliseconds)
|
(Smalltalk) computeArea1: (the dictionary) |
1487 |
(Smalltalk) computeArea4: (the array) |
440 |
(Smalltalk) computeAreaLeft:right:top:bottom: (good old keyword) |
18 |
(Ruby) computeArea() (hash form) |
~4700 |
(Ruby) computeArea() (comma list form) |
~1800 |
So in Ruby, you pay ~3.5x to use something closer to keyword style. In Smalltalk you of course get keywords, but the order is explict. Its ~100x faster than Ruby's fast form. To get the flexibility/terseness of doing it in arbitrary order, you pay a much bigger price in Smalltalk, dropping a factor of ~24 to get it. And now it's ~10x faster than Ruby. Not that this means anything really. It was just fun to play around with it. Ruby is cool (except the syntax isn't Smalltalk).