Io で非同期メッセージング

Erlang のチュートリアル の非同期メッセージングの例に触発されて、Io のアクター(“@()”)で似たようなことはできないか、Io の非同期メッセージングの学習を兼ねて試してみました。

syncFib.io

fib := method(n, if(n < 3, 1, fib(n - 2) + fib(n - 1)))
printFib := method(n, fib(n) println)
for(i, 0, 15, 5, j := 20 - i; printFib(j))

fib(20)、fib(15)、fib(10)、fib(5) を計算して結果を出力するスクリプトです。io syncFib.io で実行します。出力は次のとおり。

6765
610
55
5

送信した順に結果が出力されます。これを非同期にするには、printFib の送信時に“@@”か“@”を付けます(非同期メッセージにより将来起動されるメソッドの返値を利用したい場合は“@”、不要な場合は“@@”を使用)。

asyncFib_ng.io

fib := method(n, if(n < 3, 1, fib(n - 2) + fib(n - 1)))
printFib := method(n, fib(n) println)
for(i, 0, 15, 5, j := 20 - i; @@printFib(j))

ただ残念ながら、これだけでは同期時とまったく同じ出力になってしまいます。じつは、当初期待した動作をさせるには、次のように書き直さないといけません。

asyncFib.io

fib := method(n, yield; if(n < 3, 1, fib(n - 2) + fib(n - 1)))
printFib := method(n, fib(n) println)
for(i, 0, 15, 5, j := 20 - i; clone @@printFib(j))


以前の言語仕様では、非同期に送信されたメッセージを処理するためのスレッドは自動的に切り替わっていたようなのですが、今は仕様では yield を送信して明示的に切り換えてあげる必要があります。また、受信した非同期メッセージはオブジェクトごとに蓄積され、これは受信順に処理されます。したがって、非同期メッセージで起動するメソッドを yield しながら疑似並列処理するためには、別々のオブジェクトに送信しなければならないのです。そこで、上のスクリプトでは @@printFib のレシーバを clone しています(clone のレシーバは Lobby )。


このようにしてやっと、次のような出力を得ることができます。ここまでたどり着くのに、なんだかちょっと疲れちゃいました(^_^;)。

5
55
610
6765


ちなみに、非同期通信のない SqueakSmalltalk では、明示的にスレッド(プロセス。a Process)をフォークすることで似たようなことができます。

Integer compile: 'fibonacci
   Processor yield.
   ^ self < 3 
      ifTrue: [1] 
      ifFalse: [(self - 2) fibonacci + (self - 1) fibonacci]
' classified: #'mathematical functions'.

World findATranscript: nil.
(20 to: 5 by: -5) do: [: nn |
   [Transcript show: String cr, nn fibonacci printString] fixTemps fork]
5
55
610
6765


少々表現は冗長になりますが、fork を value に変えるだけで逐次実行版にできるのが嬉しい(?)ですね。(それをいったら、Io も @@ を取り去るだけですが…)

World findATranscript: nil.
(20 to: 5 by: -5) do: [: nn |
   [Transcript show: String cr, nn fibonacci printString] fixTemps value]
6765
610
55
5


Erlang の例にある、時間制限付きバージョンも書いてみました。

results
results := OrderedCollection new. (30 to: 5 by: -5) do: [: nn | [results add: nn fibonacci] fixTemps fork]. (Delay forMilliseconds: 500) wait. World findATranscript: nil. results inspect; do: [: result | Transcript cr; show: result]
5
55
610
6765


トランスクリプトへの出力の範囲は環境依存です。処理の終わりに results のインスペクタを起動しておきましたので、時間切れで間に合わなかった結果が、順次、self に遅れて追加されてゆくのを見ることができます。