home » 2014 » 12 » Continuations in Factor

Continuations in Factor

Posted:
2014-12-03
Tags:
continuations factor

While debugging a fun crash (#1187) in Factor, I came upon the callcc0 primitive which is how it implements continuations. It is a concept which it inherits from Scheme, where it is known as Call with current continuation.

In Factor, just like in all other programming languages, control flow is mostly linear. Execution moves forward one function (or word as it's called in Factor) at a time. The exception to that is when an exception is thrown. Then an error object is put on the stack and execution jumps out to the nearest exception handling block.

The surface-level syntax looks like this:

[ "hello!" throw ] [ print ] recover

The first quotation (the try) throws the string "hello!" as an error which is then printed out in the second one (the catch). Now try this in the listener:

IN: scratchpad \ recover see
USING: continuations.private kernel sequences ;
IN: continuations
: recover
    ( ..a try: ( ..a -- ..b ) recovery: ( ..a error -- ..b ) -- ..b )
    [ [ [ catchstack* push ] dip call catchstack* pop* ] curry ]
    dip ifcc ; inline

It shows you the definition for the recover word. With this knowledge we can directly substitute the call to recover with its definition:

[ "hello" throw ]
[ print ]
[
    [
        [ catchstack* push ] dip
        call
        catchstack* pop*
    ] curry
] dip ifcc

And since we know the definition of the try and catch quotations we can further rewrite that as:

[
    CONTEXT-OBJ-CATCHSTACK context-object push
    "hello!" throw
    CONTEXT-OBJ-CATCHSTACK context-object pop
] [ print ] ifcc

In the examples below I won't bother with the intermediate step and always simplify as much as possible. Hopefully that won't make the text to hard to follow.

As an aside, this is how Factor differs from most conventional languages such as Java or Python. In those languages you have the try-catch statement, for loops etc and those are in a way "magic". For example, if Python didn't provide you with a try-catch, you wouldn't be able to write one yourself, using only Python. In Factor you can, as this demonstration of how the try-catch is built shows.

The ifcc word is very powerful. Likely the most powerful one in the whole Factor system. When it is called, it takes a snapshot of the whole virtual machine, makes an object of it and passes it to the first quotation. That quotation then either runs to completion or throws an error, in which case the second quotation is run.

Now, let's focus on the throw word and see what we can peel of from it:

[
    CONTEXT-OBJ-CATCHSTACK context-object push
    "hello!"
    ERROR-HANDLER-QUOT special-object call
    CONTEXT-OBJ-CATCHSTACK context-object pop
] [ print ] ifcc

As you can see, throwing an exception is nothing more than calling a special quotation in the vm with the error object on the stack. But why stop here? ERROR-HANDLER-QUOT can be inspected and we can rewrite the call to that quotation with its definition:

[
    CONTEXT-OBJ-CATCHSTACK context-object push

    ! This part is throw
    OBJ-CURRENT-THREAD special-object error-thread set-global
    current-continuation error-continuation set-global
    "hello!" original-error set-global
    "hello!" rethrow

    CONTEXT-OBJ-CATCHSTACK context-object pop
] [ print ] ifcc

So all throw did was set some dynamic variables in the global namespace and call rethrow. Again let's do the same substitution of the rethrow word:

[
    CONTEXT-OBJ-CATCHSTACK context-object push

    ! This part is throw
    OBJ-CURRENT-THREAD special-object error-thread set-global
    current-continuation error-continuation set-global
    "hello!" original-error set-global
    "hello!" save-error
    "hello!" CONTEXT-OBJ-CATCHSTACK context-object pop continue-with

    CONTEXT-OBJ-CATCHSTACK context-object pop
] [ print ] ifcc

(We know that the catch stack is not empty so this rewrite is significantly simpler than the real definition of the rethrow word).

This blog post ends kind of abruptly here. :) I know it's awful of me, but unless I learn more about Factor's continuations I can't say much more.