Thursday, March 12, 2009

Playing with Blocks

Today I learned about blocks. We Smalltalkers all know and love blocks: those bits of code in square brackets that we can do all sorts of dandy tricks with. So what evils are there?


Well, Evil Trick number one:

b := [ ^ Transcript show: 'In the block'; cr ].

... in another method:

b value.
Transcript show: 'After the block evaluation'; cr.

The latter message will not be printed on the transcript. When you evaluate a block that has a return statement in it (a "non-local" return), the current context is abruptly terminated and the method context where the block was defined is returned from.


Evil trick number two:

b := [ ^ 1 ].
[ b value ] fork.

This will cause >>cannotReturn: to be evaluated on the BlockContext. The block has a non-local return in it but it cannot return. The containing MethodContext of that block is not on the call stack, because the block is evaluated in another process which has its another stack. When a non-local return happens, the VM searches down the call stack until it finds the method that the block is defined in.

This will also happen if you get a block with a non-local return by calling a method that defines it and then evaluate that block after the method returns:

someMethod
^ [ ^ 1 ].

self someMethod value. "Fails"


Evil trick number three:

s := Semaphore new.
b := [ s wait ].
[ b value ] fork.
[ b value ] fork.

This will fail in Squeak, although it shouldn't. Squeak has an optimisation that prevents two concurrent evaluations of the same block. I'm not entirely sure why, but it certainly hinders concurrent programming in Squeak (that, and the fact that Squeak is single threaded).

It also happens if you get a block to evaluate itself:

b := [:block | block value: block].
b value: b


Evil trick number four:

[ |b| b := 'bar'] value.
[ |b| ^ b] value.

You would expect to get a nil, but in Squeak, you'll get a 'bar' instead. This is because block variables are defined in their method contexts rather than in their block contexts. This is what the community is complaining about when they complain about the lack of closure support. So, how do you fix it?

[ |b| b := 'bar'] value.
[ |b| ^ b] fixTemps value.

This version does not work: fixTemps disallows a block from doing non-local returns. How about:

| result |
[ |b| b := 'bar'] value.
[ |b| result := b] fixTemps value.
^ result.

Lame, but it actually works like it should. nil is returned.

1 comment:

Frank Shearar said...

Happily,

[ |b| b := 'bar'] value.
[ |b| ^ b] value.

now results (a) in a warning about the second block's b being undefined and (b) nil being returned.