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.
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
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).
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.