アクタ

画面、一覧の一行、監視盤の計器、通信の窓口。状態を持つものを、アクタとして一つずつ分けて書きます。アクタは自分の状態だけを持ち、他のアクタとはメッセージだけでやりとりします。他のアクタの状態を直接書き換える手段はありません。

なので、ある状態を変えられるのはそのアクタだけです。おかしな値になったとき、探す場所は一つで済みます。

届かないメッセージは、動く前に分かる

メッセージはクラスで、それを処理するメソッドはクラスと同じ名前で書きます。

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: vv を状態に持つアクタを作る
ref tell: mm を送るだけで、待たない
ref ask: mm を送り、答えを後で受け取る
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.

置き場所を指定するだけで、送る側の書き方は変わりません。値は変わらないので、別のスレッドに渡すときに写す必要も、守るためのロックも要りません。手が空いたスレッドは、他のスレッドの仕事を引き取ります。

画面を描くアクタは、ウィンドウのあるスレッドで動きます。待つ処理を別のスレッドのアクタに任せておけば、待っている間も画面は止まりません。