A Function always starts at the beginning of the code inside of it, and returns a single value unless interrupted.

A Coroutine is able to yield values at different points during its execution. Each Coroutine has a separate execution state to allow it to keep track of variables inside. This sepearate execution state means that if a Coroutine fails, it does not travel outside the Coroutine.

The type of a Coroutine has two parameters: The type it yields, and the type to send it.

Creation

The Coroutine class does not have a constructor. There are two methods for creating a Coroutine, the simplest of which is Coroutine.build. This is a Coroutine that yields Integer values, but is not sent any values.

import (Coroutine) coroutine

define one_to_five(co: Coroutine[Integer, Unit])
{
    for i in 1...3: {
        co.yield(i)
    }

    co.yield(4)
    co.yield(5)
}

var co = Coroutine.build(one_to_five)

print(co.resume()) # Some(1)
print(co.resume()) # Some(2)
print(co.resume()) # Some(3)
print(co.resume()) # Some(4)
print(co.resume()) # Some(5)
print(co.resume()) # None

This is a Coroutine that receives Integer values. It is possible to send any type to a Coroutine, including a generic type.

import (Coroutine) coroutine

define yield_total(co: Coroutine[Integer, Integer])
{
    var total = 0

    for i in 1...3: {
        total += co.receive()

        co.yield(total)
    }
}

var co = Coroutine.build(yield_total)

print(co.resume_with(1)) # Some(1)
print(co.resume_with(2)) # Some(3)
print(co.resume_with(3)) # Some(6)
print(co.resume_with(3)) # None

The other method for creating a Coroutine is Coroutine.create_with_value, which allows the Function to have one argument. That argument is passed once, when the Coroutine is given. In cases where multiple arguments are needed, a Tuple can be provided.

The second method of creating a Coroutine allows allows this next example to not need a closure.

import (Coroutine) coroutine

define between(co: Coroutine[Integer, Unit],
               range: Tuple[Integer, Integer])
{
    var start = range[0]
    var end = range[1]
    var i = start

    while i <= end: {
        co.yield(i)
        i += 1
    }
}

var co = Coroutine.build_with_value(between, <[1, 3]>)

print(co.resume()) # Some(1)
print(co.resume()) # Some(2)
print(co.resume()) # Some(3)
print(co.resume()) # None

Status

The status of any Coroutine can be checked at any time through one of the Coroutine "is" methods. It will always be in one of the following states.

  • Done: The Coroutine has returned. Any value that a Coroutine returns is ignored (Coroutine.is_done).

  • Failed: An exception was raised in the Coroutine (Coroutine.is_failed).

  • Running: The Coroutine is currently executing. If one Coroutine runs another Coroutine, the first Coroutine will still be marked as running (Coroutine.is_running).

  • Waiting: Initial state for new Coroutine values, and the state after yielding (Coroutine.is_waiting).

Exceptions

Coroutine.resume and Coroutine.resume_with will never raise an exception. If the Coroutine cannot be resumed for any reason, a None is returned.

Due to how Coroutine is implemented, it is not possible to yield while inside of a foreign function. Attempting to do so will result in an exception in the Coroutine, and no value passing to the caller. In particular, Hash and List iteration methods are foreign functions, so it is not possible to yield inside of them.

Coroutine.build and Coroutine.build_with_value can technically raise RuntimeError if the Function given is not a native one. In practice, that is only possible with code like Coroutine.build(Coroutine.resume), which does not make sense anyway.