アクタ
画面、一覧の一行、監視盤の計器、通信の窓口。状態を持つものを、アクタとして一つずつ分けて書きます。アクタは自分の状態だけを持ち、他のアクタとはメッセージだけでやりとりします。他のアクタの状態を直接書き換える手段はありません。
なので、ある状態を変えられるのはそのアクタだけです。おかしな値になったとき、探す場所は一つで済みます。
届かないメッセージは、動く前に分かる
メッセージはクラスで、それを処理するメソッドはクラスと同じ名前で書きます。
Object subclass: #Add fields: (n).
Add >> n = n.
Object subclass: #Get fields: ().
Actor subclass: #Counter fields: (count).
Counter >> Add m = self copyWith: { count: count + m n }.
Counter >> Get = ( self reply: count. self ).
c := Actor spawn: (Counter fields: { count: 0 }).
c tell: (Add fields: { n: 5 }).
((c ask: (Get fields: { })) await) displayNl. "5"
Counter に処理の無いメッセージを送るコードは、動き始める前にエラーになります。送り先がどのクラスかは推論で分かっているので、型を書く必要はありません。
処理は次の状態を返します。状態は書き換えるのではなく、新しい値(copyWith:)を返します。それ以外のものを返すと、これも書いた所でエラーになります。
送り方と待ち方
Actor spawn: v | v を状態に持つアクタを作る |
ref tell: m | m を送るだけで、待たない |
ref ask: m | m を送り、答えを後で受け取る |
f await | 答えを待つ |
f awaitFor: ms | 答えを待つ。締め切りを過ぎたら諦める |
self reply: x | 答えを返す |
OS sleep: ms | 決めた時間だけ待つ |
一つのアクタは、届いた順に一つずつ処理します。同じアクタの処理が同時に走ることはないので、アクタの中にロックは要りません。
待つ処理を、上から下へ書ける
答えを待つ処理を、分けずにそのまま書けます。
Actor subclass: #Client fields: (counter).
Client >> Go = (
counter tell: (Add fields: { n: 5 }).
n := (counter ask: (Get fields: { })) await.
n displayNl.
self).
await で待っている間、そのアクタは止まりますが、他のアクタは動き続けます。答えが来たら、止まった所から続きます。答えを受け取るための別のメソッドを書いたり、続きを後で呼ばれる関数に分けたりする必要はありません。
状態ごとに、受け付けるものを分ける
接続中と切断中、入力中と確定済み。アクタの状態が切り替わると振る舞いも変わる、というものは、状態ごとにクラスを分けて書けます。
Object subclass: #Open fields: ().
Object subclass: #Close fields: ().
Actor subclass: #Closed fields: ().
Closed class >> start = Closed fields: { }.
Closed >> Open = ('opening' displayNl. self become: (Opened fields: { visits: 1 })).
Closed >> Close = ('already closed' displayNl. self).
Actor subclass: #Opened fields: (visits).
Opened >> Open = ('already open' displayNl. self copyWith: { visits: visits + 1 }).
Opened >> Close = (('closing after ' , visits asString) displayNl. self become: (Closed fields: { })).
become: で別のクラスに切り替わります。状態ごとの振る舞いがクラスごとにまとまるので、「いまどの状態か」を見て分岐するコードが要りません。
切り替わった先が、切り替わる前なら受け付けたメッセージを処理できないと、動く前にエラーになります。送る側は、相手がいまどの状態かを知らなくても安全に送れます。
決まった種類のメッセージが届くまで待つこともできます(選択受信)。ほかのメッセージは、後で処理するために取っておかれます。
一つが失敗しても、他は動き続ける
アクタの処理が途中で失敗すると、止まるのはそのアクタだけです。プログラムと他のアクタは動き続けます。
止まったことは、見張っているアクタに知らされます。どのアクタが、どんな理由で止まったかが届くので、そこで作り直したり、利用者に知らせたりできます。
Actor subclass: #Boss fields: (lost).
Boss class >> start = Boss fields: { lost: 0 }.
Boss >> Down d = (
('lost a worker: ' , d reason) displayNl.
self copyWith: { lost: lost + 1 }).
Boss >> Hire = (
w := Actor spawn: (Worker fields: { name: 'a' }).
me monitor: w.
w tell: (Work fields: { }).
w tell: (Boom fields: { }).
self).
止まったアクタに答えを待っていた側は、待ちが失敗として返ります。答えの来ない待ちに残されることはありません。
スレッドもロックも書かずに、同時に動かす
重い計算や、通信・データベースのように待つ処理は、別のスレッドに置けます。
OS startWorkerDomains: 4.
a := Actor spawn: (Worker fields: { name: 'a' }) onDomain: 1.
b := Actor spawn: (Worker fields: { name: 'b' }) onDomain: 2.
置き場所を指定するだけで、送る側の書き方は変わりません。値は変わらないので、別のスレッドに渡すときに写す必要も、守るためのロックも要りません。手が空いたスレッドは、他のスレッドの仕事を引き取ります。
画面を描くアクタは、ウィンドウのあるスレッドで動きます。待つ処理を別のスレッドのアクタに任せておけば、待っている間も画面は止まりません。