coroutines in Nelua
- co1.nelua - create, yield, resume, status, running
- co2.nelua - spawn, destroy, pop
- co3.nelua - push
- co4.nelua - pop in a coroutine
- co5.nelua - subtle errors
- co6.nelua - wrap
- co7.nelua - coroutines resuming other coroutines?
- co8.nelua - infinite 'internal' resumption
More examples: AOC 2015 day 1, examples/threads
co1.nelua - create, yield, resume, status, running
The following code works in both Lua and Nelua:
require 'coroutine'
local co = coroutine.create(function()
for i=1, 5 do
print(i) coroutine.yield()
end
print('status in', coroutine.status(coroutine.running()))
assert(true, 'abort')
end)
coroutine.resume(co)
print('status', coroutine.status(co))
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
print(coroutine.resume(co))
print('status', coroutine.status(co))
print(coroutine.resume(co))And output is very similar:
$ paste -d'|' <(nelua co1.nelua) <(luajit co1.nelua) 1|1 status suspended|status suspended 2|2 3|3 4|4 5|5 status in running|status in running true |true status dead|status dead false Coroutine not suspended|false cannot resume dead coroutine
Nelua differs in error-handling. If that assert is changed to fail, Nelua aborts:
1
status suspended
2
3
4
5
status in running
co1.nelua:7:10: runtime error: abort
assert(false, 'abort')
^~~~~
Aborted
and Lua does not:
1 status suspended 2 3 4 5 status in running false co1.nelua:7: abort status dead false cannot resume dead coroutine
co2.nelua - spawn, destroy, pop
Nelua also differs in communication: Lua coroutines communicate through yield/resume, whereas Nelua coroutines communicate through yield, push, and pop.
require 'coroutine'
local function upto()
for i=1, 5 do
coroutine.yield(i)
end
end
local co = coroutine.spawn(upto)
repeat
local n: integer
coroutine.pop(co, &n)
print(n)
coroutine.resume(co)
until coroutine.status(co) ~= 'suspended'
coroutine.destroy(co)
Output:
1 2 3 4 5
co3.nelua - push
NB. from library docs: The user is responsible to always use the right types and push/pop order and count.
require 'coroutine'
local function upto()
local co = coroutine.running()
local n = 1
for i=1, 2 do
for j=1, 5 do
print('pushing:', n)
coroutine.push(co, n)
n = n + 1
end
coroutine.yield()
end
end
local co = coroutine.spawn(upto)
repeat
for j=1, 5 do
local n: integer
coroutine.pop(co, &n)
print(n)
end
print '---'
coroutine.resume(co)
until coroutine.status(co) ~= 'suspended'
coroutine.destroy(co)Output:
pushing: 1 pushing: 2 pushing: 3 pushing: 4 pushing: 5 5 4 3 2 1 --- pushing: 6 pushing: 7 pushing: 8 pushing: 9 pushing: 10 10 9 8 7 6 ---
And with local n: number instead when popping:
pushing: 1 pushing: 2 pushing: 3 pushing: 4 pushing: 5 2.4703282292062e-323 1.976262583365e-323 1.4821969375237e-323 9.8813129168249e-324 4.9406564584125e-324 --- pushing: 6 pushing: 7 pushing: 8 pushing: 9 pushing: 10 4.9406564584125e-323 4.4465908125712e-323 3.95252516673e-323 3.4584595208887e-323 2.9643938750475e-323 ---
co4.nelua - pop in a coroutine
Communication into a coroutine is also possible:
require 'coroutine'
local function upto()
local co = coroutine.running()
for i=1, 5 do
coroutine.yield(i)
local n: integer
coroutine.pop(co, &n)
print('upto: ', n)
end
end
local co = coroutine.spawn(upto)
repeat
local n: integer
coroutine.pop(co, &n)
coroutine.resume(co, 5 - n)
until coroutine.status(co) ~= 'suspended'
coroutine.destroy(co)Output:
upto: 4 upto: 3 upto: 2 upto: 1 upto: 0
co5.nelua - subtle errors
In the Lua version of this example, the first two values (1 and 2) are lost. Here, they're recovered at the end from where they were left further down the stack:
require 'coroutine'
local function upto()
local co = coroutine.running()
for i=1, 5 do
print('upto: yielding')
coroutine.yield(i)
local n: integer, m: integer
print('upto: popping')
coroutine.pop(co, &n, &m)
print('upto:', n, m)
end
print('wind down')
local n: integer
while coroutine.pop(co, &n) do
print('upto: woah! I almost lost this:', n)
end
end
local co = coroutine.create(upto)
local n: integer
--print('outerco:', coroutine.pop(co, &n)) print('outer:', n) --fails
print('outer: resuming') coroutine.resume(co, 1, 2)
coroutine.pop(co, &n) print('outer:', n)
print('outer: resuming') coroutine.resume(co, 3, 4)
coroutine.pop(co, &n) print('outer:', n)
print('outer: resuming') coroutine.resume(co, 5, 6)
coroutine.pop(co, &n) print('outer:', n)
print('outer: resuming') coroutine.resume(co, 7, 8)
coroutine.pop(co, &n) print('outer:', n)
print('outer: resuming') coroutine.resume(co, 9, 10)
coroutine.pop(co, &n) print('outer:', n)
print('outer: resuming') coroutine.resume(co, 11, 12)
coroutine.pop(co, &n) print('outer:', n)
coroutine.destroy(co)Output:
outer: resuming upto: yielding outer: 1 outer: resuming upto: popping upto: 3 4 upto: yielding outer: 2 outer: resuming upto: popping upto: 5 6 upto: yielding outer: 3 outer: resuming upto: popping upto: 7 8 upto: yielding outer: 4 outer: resuming upto: popping upto: 9 10 upto: yielding outer: 5 outer: resuming upto: popping upto: 11 12 wind down upto: woah! I almost lost this: 2 upto: woah! I almost lost this: 1 outer: 5
co6.nelua - wrap
Lua has a coroutine.wrap() that returns a function which provides a convenient - but limited - interface to a coroutine. Nelua does not provide this function.
require 'coroutine'
local Wrap = @record{
co: coroutine,
fn: function(),
}
function Wrap:call()
coroutine.resume(self.co)
local n: integer
assert(coroutine.pop(self.co, &n))
return n
end
local function wrap(fn: function()): Wrap
return {
co = coroutine.create(fn),
fn = fn,
}
end
local c = wrap(function()
for i=1, 5 do
coroutine.yield(i)
end
end)
print(c:call())
print(c:call())
print(c:call())
print(c:call())
print(c:call())
print(c:call()) -- runtime errorOutput:
1
2
3
4
5
co6.nelua:11:23: runtime error: Not enough space
assert(coroutine.pop(self.co, &n))
^~~~~~~~~~~~~
Aborted
co7.nelua - coroutines resuming other coroutines?
In the following program you can see that control flow follows coroutine.resume deeper through 'a', 'b', and 'c' coroutines, and then winds back out through them, similar to function calls. And similar to function calls (without TCO), recursive resuming would eventually overflow the stack. Note the commented line in 'c': pushing and then returning has nearly the same effect as yielding there, except a separate resume would be necessary to terminate the function. Yielding is also pushing to the coroutine's own stack, so that other code can pop from that stack.
require 'coroutine'
require 'hashmap'
local registry: hashmap(string, coroutine)
registry['a'] = coroutine.create(function(n: integer)
print '>>> a'
coroutine.resume(registry['b'], n+1)
print '<<< a'
end)
registry['b'] = coroutine.create(function(n: integer)
print '>>> b'
coroutine.resume(registry['c'], n+1)
print '<<< b'
end)
registry['c'] = coroutine.create(function(n: integer)
print '>>> c'
--coroutine.yield(n)
coroutine.push(registry['c'], n)
print '<<< c'
end)
coroutine.resume(registry['a'], 1)
local result: integer
coroutine.pop(registry['c'], &result)
print('result:', result)
print('a:', coroutine.status(registry['a']))
print('b:', coroutine.status(registry['b']))
print('c:', coroutine.status(registry['c']))co8.nelua - infinite 'internal' resumption
require 'coroutine'
require 'io'
local a: coroutine, b: coroutine, c: coroutine
local tell: coroutine
a = coroutine.create(function()
while true do
io.stdout:printf 'a'
tell = b
coroutine.yield()
end
end)
b = coroutine.create(function()
while true do
io.stdout:printf 'b'
tell = c
coroutine.yield()
end
end)
c = coroutine.create(function()
while true do
io.stdout:printf 'c'
tell = a
coroutine.yield()
end
end)
tell = a
while true do
coroutine.resume(tell)
end