What's key about a word?

September 7, 2006 0:33:33.814

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).

Comments

Ada has keyword arguments

[Kevin Hostelley] September 7, 2006 9:16:47.023

Ada has keyword arguments that can be in any order.

p.s.

    Any chance I get to promote Ada I do. :-)  If I have to use a statically typed language then I want it to be Ada. 

Too high a price to pay

[Carl Gundel] September 7, 2006 11:59:36.760

This is a fun exploration.  :-)

The calling convention you propose is interesting, but the coding style of the called method is much too noisy.

computeArea1: aDictionary
 ^((aDictionary at: #right) - (aDictionary at: #left))
  * ((aDictionary at: #bottom) - (aDictionary at: #top))
 

 I would be unwilling to abandon the clarity of:

computeAreaRight: right left: left bottom: bottom top: top
  ^(right-left)*(bottom-top)

Perhaps something could be done with DNU (and maybe a subclass of Dictionary) to change the code to this. 

computeArea1: aDictionary
 ^(aDictionary atRight - aDictionary atLeft)
  * (aDictionary atBottom - aDictionary atTop)
 

Not sure if this is actually better, and it would have performance implications of its own.