Talking Meta

Meta talk about Smalltalk, Seaside, Magritte, Pier and related things.

Adding call/answer in under 35 lines of Dart code

This is a follow up to Seaside in under 100 lines of Dart code. Over the past few days, I’ve added some more examples and a bunch of new features — such as the ability for sessions to expire — to the GitHub Repository.

The most interesting change is another key-defining feature of Seaside: the ability to create a flow of components in normal code. While I agree with critics that this is rarely useful in web applications, it is technically an interesting challenge to implement in many programming language; and I think with Dart we can get quite close.

Imagine we want to ask the user for two numbers, and then present the sum. This is very easy to do on the command-line or with a desktop application, but on the web it turns out to be quite involved. Essentially I’d like to write:

call-answer-cc.png

And with call_answer.dart we can. I’ve added ...

  • a typed mixin CanAnswer<T> that can be added to components so they can answer values of type T;
  • an abstract component class Task that can be used to describe the flow by defining a run method; and
  • a bunch of helper methods to call other components, and to answer results back to their caller.

This works out of the box. Unfortunately Dart Futures are not resumable, and the back button causes a server exception. Dart supports the creation of custom Future classes, maybe there is a way to tweak the system by using a custom implementation instead?

In any case, easy enough I can use continuation-passing style that works as expected: both for splitting a session into multiple paths and with the back button:

call-answer-cps.png

Posted by Lukas Renggli at 28 July 2020, 5:51 pm with tags dart, seaside, web, app, continuation comment link

Continue and Break in Ottawa

The second part of the Superpowers Workshop of the Ottawa Smalltalk user’s group caught my interest. The attendees are discussing on how to implement break and continue in Smalltalk.

Unfortunately the quality of the video was not good enough to be able to steal their example, so I had to build my own. The commented parts should trigger the well known loop operations and continue with the next iteration of the loop, respectively break the execution of the loop.

1 to: 5 do: [ :index |
index = 2
ifTrue: [ " continue " ].
index even
ifTrue: [ " break " ].
Transcript show: index; cr ]

The above example exercises the two hypothetical constructs. If you execute the code in your brain you should print out the numbers 1 and 3 on the transcript.

The first possible solution that comes to my mind are exceptions. Smalltalk has very powerful exceptions that can do all kind of magic things, but here we just need to signal and catch them at the right place. I created two new exception classes ContinueLoop and BreakLoop, then I put a handler inside the loop to continue, and a handler outside the loop to break. This gives us the first working solution. Admittedly it looks very ugly:

[ 1 to: 5 do: [ :index |
[ index = 2
ifTrue: [ ContinueLoop signal ].
index even
ifTrue: [ BreakLoop signal ].
Transcript show: index; cr ]
on: ContinueLoop
do: [ :err | ] ] ]
on: BreakLoop
do: [ :err | ]

Somebody of the Ottawa Smalltalk user’s group suggested to use continuations and they eventually come up with a pretty complicated solution. In fact, if you have the Seaside classes loaded in your image, the use of continuations provides a pretty strait-forward solution to the problem. You wrap the complete loop into a continuation to break, and the body of the loop in a continuation to continue. Evaluating the block arguments break and continue jumps right to the place after you created the respective continuations and resumes execution from there. This gives us the expected behavior:

Continuation currentDo: [ :break | 
1 to: 5 do: [ :index |
Continuation currentDo: [ :continue |
index = 2
ifTrue: [ continue value ].
index even
ifTrue: [ break value ].
Transcript show: index; cr ] ] ]

The solution is quite readable, but it basically uses a sledgehammer to crack the nut. Also continuations are pretty heavyweight to use in tight loops like this, because they essentially create a snapshot of the complete execution stack. And in this example we are not interested into the stack at all, we just want to jump back to the place where the continuation was defined. Luckily jumping back is something plain old Smalltalk blocks can do as well:

withEscaper: aBlock
aBlock value: [ ^ nil ]

This small helper methods evaluates aBlock and passes another block into it that returns nil. It is important to note that a return in Smalltalk always returns from the lexical scope, that means it returns from the method the return was defined in. In this example, evaluating the block [^ nil ] returns from the method #withEscaper:. Of course, this only works if the method, #withEscaper: is still on the execution stack (if not, we had to use continuations).

We can use the above helper method to implement the final version of our loop. This time without using magic, but simple and fast Smalltalk blocks:

self withEscaper: [ :break | 
1 to: 5 do: [ :index |
self withEscaper: [ :continue |
index = 2
ifTrue: [ continue value ].
index even
ifTrue: [ break value ].
Transcript show: index; cr ] ] ]
Posted by Lukas Renggli at 20 October 2009, 9:13 pm with tags smalltalk, continuation 4 comments link