この初版はLua 5.0向けに書かれています。後のバージョンでも大部分は関連性がありますが、いくつかの違いがあります。
第4版はLua 5.3を対象としており、Amazonや他の書店で入手できます。
本書を購入することで、Luaプロジェクトの支援にもなります。


9.1 – コルーチンの基礎

Luaは、すべてのコルーチン関数をcoroutineテーブルにまとめて提供しています。create関数は新しいコルーチンを作成します。この関数は、コルーチンが実行するコードを含む関数を唯一の引数として取ります。戻り値は、新しいコルーチンを表すthread型の値です。多くの場合、createへの引数は、以下のように無名関数です。

    co = coroutine.create(function ()
           print("hi")
         end)
    
    print(co)   --> thread: 0x8071d98

コルーチンは、中断、実行中、死亡の3つの状態のいずれかになります。コルーチンを作成すると、中断状態で開始されます。つまり、コルーチンは作成時に自動的に本体を実行しません。status関数を使用して、コルーチンの状態を確認できます。

    print(coroutine.status(co))   --> suspended
coroutine.resume関数は、コルーチンの実行を(再)開始し、状態を中断から実行中に変更します。
    coroutine.resume(co)   --> hi
この例では、コルーチン本体は単に"hi"を出力して終了し、コルーチンを死亡状態のままにします。この状態からは復帰できません。
    print(coroutine.status(co))   --> dead

これまでのところ、コルーチンは関数を呼び出す複雑な方法のように見えます。コルーチンの真の力は、実行中のコルーチンが実行を中断して後で再開できるようにするyield関数にあります。簡単な例を見てみましょう。

    co = coroutine.create(function ()
           for i=1,10 do
             print("co", i)
             coroutine.yield()
           end
         end)
このコルーチンを再開すると、実行が開始され、最初のyieldまで実行されます。
    coroutine.resume(co)    --> co   1
状態を確認すると、コルーチンが中断されているため、再び再開できることがわかります。
    print(coroutine.status(co))   --> suspended
コルーチンの視点から見ると、中断中に発生するすべてのアクティビティは、yieldの呼び出し内で発生しています。コルーチンを再開すると、このyieldの呼び出しが最終的に戻り、コルーチンは次のyieldまたは終了まで実行を続けます。
    coroutine.resume(co)    --> co   2
    coroutine.resume(co)    --> co   3
    ...
    coroutine.resume(co)    --> co   10
    coroutine.resume(co)    -- prints nothing
最後のresume呼び出し中に、コルーチン本体はループを終了して戻ったので、コルーチンは現在死亡しています。もう一度再開しようとすると、resumefalseとエラーメッセージを返します。
    print(coroutine.resume(co))
    --> false   cannot resume dead coroutine
resumeは保護モードで実行されることに注意してください。そのため、コルーチン内でエラーが発生した場合、Luaはエラーメッセージを表示せず、代わりにresume呼び出しに返します。

Luaの便利な機能は、resume-yieldのペアがそれらの間でデータを交換できることです。対応するyieldを待機していない最初のresumeは、追加の引数をコルーチンメイン関数の引数として渡します。

    co = coroutine.create(function (a,b,c)
           print("co", a,b,c)
         end)
    coroutine.resume(co, 1, 2, 3)    --> co  1  2  3
resumeの呼び出しは、エラーがないことを示すtrueの後に、対応するyieldに渡された引数を返します。
    co = coroutine.create(function (a,b)
           coroutine.yield(a + b, a - b)
         end)
    print(coroutine.resume(co, 20, 10))  --> true  30  10
対称的に、yieldは、対応するresumeに渡された追加の引数を返します。
    co = coroutine.create (function ()
           print("co", coroutine.yield())
         end)
    coroutine.resume(co)
    coroutine.resume(co, 4, 5)     --> co  4  5
最後に、コルーチンが終了すると、メイン関数によって返された値は、対応するresumeに渡されます。
    co = coroutine.create(function ()
           return 6, 7
         end)
    print(coroutine.resume(co))   --> true  6  7

同じコルーチンですべての機能を使用することはめったにありませんが、すべてに用途があります。

コルーチンについてすでに知っている人のために、先に進む前にいくつかの概念を明確にしておくことが重要です。Luaは、私が*非対称コルーチン*と呼ぶものを提供しています。つまり、コルーチンの実行を中断する関数と、中断されたコルーチンを再開する別の関数があります。他の言語の中には、*対称コルーチン*を提供するものがあり、任意のコルーチンから別のコルーチンに制御を移すための関数は1つだけです。

非対称コルーチンを*セミコルーチン*と呼ぶ人もいます(対称ではないため、実際には*co*ではありません)。ただし、*セミコルーチン*という同じ用語を使用して、コルーチンが補助関数内にない場合、つまり制御スタックに保留中の呼び出しがない場合にのみ実行を中断できる、コルーチンの制限された実装を示す人もいます。言い換えれば、そのようなセミコルーチンのメインボディのみがyieldできます。Pythonの*ジェネレーター*は、この意味でのセミコルーチンの例です。

対称コルーチンと非対称コルーチンの違いとは異なり、コルーチンとジェネレーター(Pythonで提示されているように)の違いは根本的なものです。ジェネレーターは、真のコルーチンで記述できるいくつかの興味深い構成を実装するのに十分強力ではありません。Luaは、真の非対称コルーチンを提供します。対称コルーチンを好む人は、Luaの非対称機能の上にそれらを実装できます。それは簡単な作業です。(基本的に、各転送はyieldの後にresumeを実行します。)