Topic 04-coroutines.md

Coroutines

Although coroutines are sometimes called threads (and type returns ‘thread’ as their type) they are not operating system threads as usually understood. (Lua sometimes runs on machines that don’t even have the luxury of an operating system.)

Rather, a coroutine is another kind of ‘function with memory’. Once a coroutine is created from a regular Lua function, it can be resumed; the coroutine yields and waits for the caller to resume it again. Unlike a function which is repeatedly called, it resumes exactly at the point where it last yielded.

 function coco()
     print '1'
     coroutine.yield(1)
     print '2'
     coroutine.yield(2)
     print '3'
     coroutine.yield(3)
 end

 c = coroutine.create(coco)
 print(coroutine.resume(c))
 print(coroutine.resume(c))
 print(coroutine.resume(c))

 ----> output:
 1
 true    1
 2
 true    2
 3
 true    3

The first resume call causes the first yield call (after printing 1) and we get the value that was passed to yield. The second resume call makes the yield return and we print out 2, etc. The key idea is that the coroutine remembers where it was and resumes where it last was executing.

So each coroutine preserves its complete state, and is sleeping when not explicitly resumed. This is often called ‘cooperative multitasking’ because one coroutine has to yield for another coroutine to resume.

coroutine.wrap will construct a function which resumes a coroutine.

 f = coroutine.wrap(coco)
 print(f()) -- 1
 print(f()) -- 2
 print(f()) -- 3
 print(f()) -- nil

(This is convenient, but if there is a problem an error will be raised and will need to be handled. With coroutine.resume the first returned value is the status; if it’s false then the second value is the error, just like pcall .)

This is exactly what Lua considers a simple iterator: a function which can be repeatedly called, returning new values and indicating the end with a nil.

Coroutines help in making iterators where the logic is not so straightforward as the previous examples. Consider the following data structure:

 T = {
     value = 10,
     left = {
         value = 20,
         left = {value = 40},
         right = {value = 50},
     },
     right = {
         value = 30
     }
 }

This kind of structure is often called a tree, in this case a binary tree because there are at most two branches at each note. The end nodes are often called leaves because they have no branches.

A recursive function for printing all the values is easy to write

 function traverse(t)
     if t.left then traverse(t.left) end
     print (t.value)
     if t.right then traverse(t.right) end
 end

 traverse(T)
 -->
 40
 20
 50
 10
 30

This function will keep visiting the left branches until there isn’t any, so it first visits left = {value = 20.. and then left = {value = 40}; this node has no left so it prints out value (40), and so on.

Now comes the key part: replace the print with coroutine.yield and turn it into a coroutine:

 function traverse(t)
     if t.left then traverse(t.left) end
     coroutine.yield (t.value)
     if t.right then traverse(t.right) end
 end

 c = coroutine.create(traverse)
 print(coroutine.resume(c,T)) -- true   40
 print(coroutine.resume(c,T)) -- true    20

This can be made into an iterator if we make a function of no arguments from this coroutine:

 function tree_iter(t)
     return coroutine.wrap(function()
         return traverse(t)
     end)
 end

 for v in tree_iter(T) do print(v) end
  -->
 40
 20
 50
 10
 30

tree_iter returns the iterator. Each time it is called, the coroutine yields, returning the value of the node. We keep yielding until the function finishes, when it returns nil.

!feels a little difficult, not so?!

Next: Programs

generated by LDoc 1.3