Prexonite Script

a .NET hosted scripting language with a focus on meta-programming and embedded DSLs

View the Project on GitHub SealedSun/prx

Coroutines in Prexonite

Posted on 2007-04-16

Like the title reads: The Prexonite Scripting language and the virtual machine now support coroutines, routines with multiple entry points. They can be used to implement iterators, generators and infinite lists.


coroutine map(ref f, xs)
{
    foreach(var x in xs)
        yield f(x);
}

As coroutines exist in parallel to other routines, they cannot be called like normal functions. The calling function needs a reference to an instantiation of a coroutine in order to pass on control. Calling map therefor just returns a coroutine reference and does not execute any code.


function main()
{
    var xs = ~List.Create(1, 2, 3);
    var twice = map( x => 2*x, xs);
    foreach(var t in twice)
        println(t);
}

The variable twice is not a list with modified elements but a reference to a call to map with the arguments x => 2*x (the mapping function) and xs (the list to operate on). No calculations have been performed so far.

The foreach loop exploits the fact that coroutines implement the IEnumerable interface. When the first element is needed, control gets passed over to the coroutine, which executes like any other function. At least until it hits the yield statement. At that point, a value is returned to the calling function which assigns this value to the iterator variable.

The magic starts when the next element is requested. Then, control is again passed to the coroutine but instead of starting at the beginning like normal functions, execution continues at the point where the routine has been left before, right after the yield statement.

Instead of using the foreach pattern, you can also request results manually by "calling" the reference indirectly.


function main2()
{
    var xs = ~List.Create(1, 2, 3);
    var twice = map( x => 2*x, xs);
    while(twice.IsValid)
        println(twice.());
}

The results are the same.

Inner workings

What you saw in the first sample is just syntactic sugar for the following:


function map(ref f, xs)
{
    function cor()
    {
        foreach(var x in xs)
            yield f(x);
    }
    return coroutine -> cor;
}

Here, the coroutine keyword is used to create a new instance of the local function cor. You can think of this line as "calling the function but returning a reference to that call instead of executing code".

You can pass anything to the coroutine construct as long as it can generate a stack context (implements IStackAware). You could even create coroutines from normal functions but they would not return a single result since they lack the yield statements. Values passed to return are not returned by coroutines.

It is also possible to use coroutines with parameters. You would have to pass the arguments when instantiating the coroutine.


function map(ref f, xs)
{
    foreach(var x in xs)
        yield f(x);  
}

function main()
{
    var xs = ~List.Create(1, 2, 3);
    var twice = coroutine -> map for( x => 2*x, xs);
    foreach(var t in twice)
        println(t);
}

The last sample is a translation of the sample found in the wikipedia article about coroutines.


declare function
    produce\impl,
    consume\impl,
    create,
    use;

var q = new ~Queue;
ref produce = coroutine produce\impl
ref consume = coroutine consume\impl

function produce\impl()
{
    while(Not q.IsFull)
    {
        var item = create();
        q.Enqueue(item);
        consume;
    }
}

function consume\impl()
{
    while(Not q.IsEmpty)
    {
        var item = q.Dequeue();
        use(item);
        produce;
    }
}

But note that this would not work since it would a) run infinitely and b) result in a stack overflow in it's current implementation.