Coroutines
The current (userspace) program state of an elymas program consists of the heap, the data stack, the call stack and the current instruction pointer. The data stack is the stack, programs are manipulating all the time, whereas the call stack holds information about what functions to return to after the current one finishes and what the current local scope is.
The coroutine functions offer ways to create and switch to new program states called coroutines which have separate call and possibly data stacks.
!!
This takes a function objects from the stack and initializes a new coroutine with an empty call and data stack. This new coroutine is returned.
{ "Hello World" dump * } !! ==coroutine
coroutine *
"Hello World"
Calling Coroutines
Coroutines can be called either with *, in which only the call stack is switched, but
the same data stack is used as before the call. However, before the coroutine continues
execution, the coroutine from which the call originated is pushed to the data stack, so
the called coroutine can return to it (instead of running into an empty call stack at
the end of the execution).
Observe how execution switches between the coroutine and the implicit initial coroutine:
{ "Hello" dump * "World" dump * } !! ==coroutine
coroutine * " " dump *
"Hello"
" "
"World"
Alternatively, ! can be used to call a coroutine and explicitely move items to the
target coroutine's data stack.
{ ==ret "42" -01 { 1 } { "Holding:" dump -101 dump "Received:" dump dump ret 0 ! =ret } loop } !! ==coroutine
"23"
/foo coroutine 1 ! --
"Holding:"
"42"
"Received:"
"foo"
/bar coroutine 1 ! --
"Holding:"
"42"
"Received:"
"bar"
"On main stack:" dump dump
"On main stack:"
"23"
!!'
The purpose of this function is to create coroutine with a cloned call stack. The created
coroutine will return to the call site of !!' after execution. However, the original
call will also return after !!' as usual, so to disambiguate the two more easily,
!!' takes two function objects. The topmost one c becomes the coroutine and pushed
to the data stack, the second argument then gets called in the usual fashion, thereby
receiving the coroutine version of c on the data stack.
Note how the greeting gets dumped twice as the coroutine continues execution after
!!':
{
{ } =*coroutine
{ =coroutine } { "coroutine" dump } !!'
"Hello World" dump
coroutine
} *
"Hello World"
"coroutine"
"Hello World"
Uses of Coroutines
One typical use of coroutines is separation between value generation and processing:
{ ==r
1 1 { 1 } { _ r 1 ! =r -010 add } loop
} !! { 0 ! -- }_ =*fib
fib dump
0000000000000001
fib dump
0000000000000002
fib dump
0000000000000003
fib dump
0000000000000005
fib dump
0000000000000008
While this seem a bit pointless in the example above, instead of calculating the fibonnacci sequence, the coroutine might be tasked with more complex sequences, in particular ones where local state is more complex than just two integers.
Another use case of coroutines is creating checkpoints in the code where execution can restart if something goes wrong - or just because you love gotoesque execution flow:
{
{
{ } { -- restart } !!'
} /restart deffd
0 ==i
restart ==checkpoint
i 1 add =i
i dump
i 7 lt { checkpoint 0 ! } rep
} *
0000000000000001
0000000000000002
0000000000000003
0000000000000004
0000000000000005
0000000000000006
0000000000000007
A third use would be thread-like processing of network requests, in particular if network sessions are stateful in a complex way.
