aboutsummaryrefslogtreecommitdiff
path: root/doc/coroutines.md
blob: 1d84130ecaa09150082e8ef51ed12dbb62a03189 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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.