Tuesday, March 21, 2006

More Block Thoughts

So, I whipped out the following code to get words from a character stream (input). I read the next word (all alpha-numeric characters) and return it or nil if none were found. Simple, huh? Here's my implementation:
nextWord
| wordString |
wordStream := String new writeStream.
input do:
[:next |
next isAlphaNumeric
ifTrue: [wordStream nextPut: next]
ifFalse:
[wordString := wordStream contents.
wordString isEmpty ifFalse: [^Word on: wordString]].
wordString := wordStream contents.
^wordString isEmpty
ifTrue: [nil]
ifFalse: [Word on: wordString]

Simple, but the duplication in it was bugging me. So, I thought, "If I made a block that did the duplicated code, what would that look like?" I quickly made my changes and it looked like this:
nextWord
| return |
wordStream := String new writeStream.
return := [| wordString |
wordString := wordStream contents.
wordString isEmpty ifFalse: [^Word on: wordString]].
input do:
[:next |
next isAlphaNumeric
ifTrue: [wordStream nextPut: next]
ifFalse: [return value]].
return value.
^nil

Even simpler! This has a distinctive functional programming style to it. I'm using the block to return from the method which is unusual to be used liked this. But, it was in the spirit of "saying it only once". You might think the return could be moved out of the block, but I only want to return if wordStream contents are empty.

I could have done the same thing with exceptions, but it would been cumbersome in such a small method. The duplication is gone and the meat of the method is clear. Exceptions are useful when returns need to happen in nested method calls.

The fun thing about this example is that you could use blocks with return to bail out of searches instead of using Exceptions. Of course, you can do the same thing in Ruby. This is the first opportunity to explore this and like all tricks you should think about its use before you do it. I thought it fit perfectly for this method.

Blocks and closures are too cool. They can help in so many ways. But, they need to be cheap.

1 comment:

Vincent Foley said...

I agree that closures need to be cheap. But not just resource-wise: syntactically cheap too. Macros are used a lot in Common Lisp I believe to avoid typing #'(lambda (x) ...) all the time. Since in Smalltalk closures are about as succint as they can be, they tend to be used more than in other languages.

Another language that uses blocks a lot is Ruby, and that's thanks to the yield keyword and being able to send a block to a method without the whole Proc.new {} (done by prefixing the block argument with a &).

When you make it short and sweet to type blocks, people will use them all the time. And that's a good thing, of course, blocks rule!