アクタ
アクタの書き方
状態を持つものはアクタです。クラスを一つ書き、フィールドに状態を並べます。メソッドがそのままメッセージハンドラで、ハンドラは次の状態を返します。
Actor subclass: #Cart fields: (items : Array[Str]).
Cart class >> new = Cart fields: { items: #[] }.
Cart >> add: item = self with: { items: items copyWith: item }.
Cart >> count = self reply: items size.
self with: { ... } は、書いた欄だけを差し替えた新しい状態を返します。元の状態は変わりません。返事が要るハンドラは self reply: を使います。
値を書き換える方法はありません。状態が変わるのは、ハンドラが次の状態を返したときだけです。
送り方は二つ
c := Actor spawn: Cart new.
c tell add: '六角ボルト'. "送るだけ。待たない"
(c ask count) forced printNl. "返事を待つ"
tell は送って終わりです。ask は Future を返し、forced でその場で待ちます。
ハンドラの中で待つと、そのアクタは返事が来るまで次のメッセージを取りません。 画面全体が止まるわけではありません。他のアクタは動き続け、イベントも落ちません。100ms かかる照会を三件投げたアクタは 104ms、207ms、320ms と順に処理し、その間も別のアクタは 3ms、70ms、136ms で動いていました。
メッセージは送った順に届きます。待っている間に届いたものは、返事のあとで順番どおりに処理されます。
待たない書き方
通信の返事を、自分宛のメッセージに変えられます。こう書くと、ハンドラはその場で終わります。
Screen >> load = (
(Http get: 'orders.json') then: [:r | me ref tell loaded: r].
self ).
Screen >> loaded: r = self with: { rows: ... }.
通信している間も画面は操作に答えます。WebSocket も同じ形にします。コールバックの中では me ref tell arrived: を投げるだけにします。
落ちたとき
try はありません。ハンドラの中でエラーが出ると、そのアクタは止まります。止まったことは、見ている側に届きます。
Boss >> start = ( me monitor: worker. self ).
Boss >> down: d = self with: { log: log copyWith: d actor name , ' が落ちた' }.
monitor: は知らせを受け取ります。link: は自分も一緒に止まります。監督役を作りたいときは、down: を受け取って代わりを spawn: する普通のアクタを書きます。
止まるのはそのアクタだけです。他のアクタの状態も、画面の他の場所も、影響を受けません。
状態が変わるときはクラスを変える
become: で別のクラスになれます。閉じた扉と開いた扉のように、状態によって受け付けるメッセージが変わる場合に使います。
Door >> open = self become: (OpenDoor fields: {}).
何を保たなければならないかは、参照の型で決まります。
Actor spawn: で作った参照は ActorRef[Door] です。Door が受け付けるものを全部受け付け続けるので、遷移先もそれを全部持っていなければなりません。足りなければコンパイルエラーです。
Actor spawn:as: でプロトコルとして作った参照は、そのプロトコルの分しか受け付けません。この場合、遷移先はプロトコルの分だけ実装すれば済みます。それ以外のハンドラを持っていても、外からは届きません。
互いに待ち合ったとき
二つのアクタが互いの返事を待つと、どちらも永久に動きません。誰も落ちていないので監視も鳴りません。
そこで ask は、送る前に相手が誰を待っているかを辿ります。辿った先が自分に戻ってきたら、その場でエラーにします。
Pong#2 asked Ping#3, which is waiting on it — nobody can take a turn
見つかったものは必ず閉路です。ただし、この検査は同じスレッドの中だけです。 Web Worker の上のアクタと待ち合った場合は見つかりません。スレッドを跨いで互いに ask する形は避けてください。