Lua Notes

nelua coroutines
Login

coroutines in Nelua

  1. co1.nelua - create, yield, resume, status, running
  2. co2.nelua - spawn, destroy, pop
  3. co3.nelua - push
  4. co4.nelua - pop in a coroutine
  5. co5.nelua - subtle errors
  6. co6.nelua - wrap
  7. co7.nelua - coroutines resuming other coroutines?
  8. 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 error

Output:

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