Sunday, October 16, 2011

Dart: Block return revisited

One of the things that was in my initial list of things I missed in Dart was the ability to return from a closure and return to the original outer scope, as Smalltalk blocks do. Thats's important if you're defining all your control structures in terms of closures. e.g.
   input isEmpty ifTrue: [^self]
That doesn't do you a lot of good as a guard clause if the return only returns from inside the block and just continues execution on the next line. Another place non-local return is useful is to short-circuit collection iteration. So to find the first element in a collection that satisfies some condition we can write it as
   detect: aBlock
      aCollection do: [:each | (aBlock value: each) ifTrue: [^each]].
      ^'not found'
The if statement motivation isn't so important in Dart because they have "if" as a syntactic construct and if you return from within the action clause it's part of the same method, not in a closure.
For iteration, Dart has three mechanisms, two of them syntactic, and one that's just a method. The first is the old-style "for" loop
   for (i=0;i<=something;i++) { print(i); }
The second is also a "for" loop, but with special syntax that iterates over each element of a collection
   for (var each in aCollection) { print(each)}
Finally, there's a forEach method listed in the Collection interface that takes a closure and applies it for each element
    aCollection.forEach( (each) => print(each);
With the first two forms they're part of the syntax, so a return statement will break out of the loop. But if you're invoking the forEach method then there isn't a way to break out of it.

The interesting bit I discovered today is that if you write your own collection classes, you can still use any of the forms. The implementation of the second form is that it sends the iterator() message to the collection and then uses that iterator to loop. So I could write a trivial binary tree class, define an Iterator (two methods: hasNext() and next()) for it and write
   for (var x in tree) { print(x); }
and it works fine.

I have to say both that I'm impressed that that works and that this takes a bit of the edge off my wanting non-local return. There are other uses, but I do have to admit that it's a complicated feature with some difficult edge cases, and being able to use it this way does take the air out of the most obvious motivating use case. Wanting to have an at:ifAbsent: where in the ifAbsent case I return from the original outer scope might be useful, but it's not nearly as good an example.