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
defmodule Coro do
@doc "Create a new coroutine"
@spec create((pid -> any())) :: pid
def create(f) do
IO.puts "create"
coro_driver = fn ->
# Block until we are resumed
receive do
{:resume, resumer} ->
IO.puts("Resumed for the first time")
f.(resumer)
other -> raise "Invalid initial resume"
end
end
spawn(coro_driver)
end
@doc "Yield to the thing that resumed this coro"
@spec yield(pid, any) :: nil
def yield(resumer, value) do
# Having to pass the resumer explicitly is wonkity but it's a start
IO.puts "yield"
send(resumer, {:yield, self(), value})
# Block waiting for resumption
receive do
{:resume, resumer} -> IO.puts("wossname")
other -> raise "Invalid resume"
end
end
@doc "Resume the given coro"
@spec resume(pid) :: any
def resume(coro) do
IO.puts "resume"
# We need to monitor a process to detect if it quits without yielding to us,
# see https://www.erlang.org/doc/reference_manual/processes#monitors
:erlang.monitor(:process, coro)
send(coro, {:resume, self()})
receive do
# The coro yielded to us
{:yield, pid, value} ->
IO.puts("Coro #{inspect pid} yielded to us with value #{inspect value}")
# The coro exited without yielding
{:DOWN, ref, _process, _pid2, reason} ->
IO.puts "Coro exited normally #{inspect reason}"
:erlang.demonitor(ref)
other -> raise "aw heck: #{inspect other}"
end
end
end
x = Coro.create(
fn resumer ->
IO.puts "Hello world from inside a coro"
Coro.yield(resumer, :hi)
3
end)
Coro.resume(x)
Coro.resume(x)