技術

同期に特化した言語を考えてみる(1)

同期に特化した言語を考えてみる(1)

はじめに

この記事は,考え途中のアイデアをそのまま書き出したものです。
読んだからと言って,何か結論が得られるものではありません。


動機

普段プログラムを書く際,各プログラムのインターフェースを定義した後に,それぞれのプログラムを書き始めると思います。

しかしながら,開かれたシステムでない限り,そのインターフェースは実装の結果生まれた物でしかないと思うのです。 実装に取り掛かる前にノード間通信を定義させられるのは,早すぎる具体化なのではないでしょうか。


ここで言う開かれたシステムというのは,例えばWWWのことです。 もし仮に,HTTPの定義を皆が勝手に拡張していたら,世の中のブラウザは今ほど便利ではなかったでしょう。 TCPで通信しているところに意味不明なトラフィックが流入していたら,TCPのスライディングウィンドウが一瞬で閉じてしまい,快適に通信できなかったでしょう。

このように「よそ」と繋がる可能性のある存在は,自身の解する言語を事前に表明しておく必要があります。

ここで,システムが開いているか閉じているかというのは,ネットワークのレイヤーによるのではないかという考えが生じます。 実際,OSI参照モデルの7層(アプリケーション層)は,閉じていると言っていい物が殆どでしょう。 ここで言う開いている/閉じているというのは,二値を取る性質のものではないことに注意してください。


話を戻しましょう。

協調動作する複数のプログラムを書く際に,「各プログラムの入出力を定義する」のは,閉じたシステムの内側に立ち入る行為です。 実際に必要とされているものは,「システム全体」と「外界」の間の通信のみを定義すれば開発を開始できるような存在ではないでしょうか。


実際のところ,「システム全体」と「外界」の通信というのは,従来の開発手法を文脈とすると余りに自明です。だって「各プログラムの入出力」に含まれていますからね。

それでは,複数のプログラムからなるシステムの内側を記述するような存在について考えると,これがなかなか難しいのです。


素朴な考え

例えば,サーバ(Sとします)に2つのクライアント(それぞれA,Bとします)が接続しているゲームを考えます。

「AがBを攻撃した」という処理を行う時,全体としては「b.hp -= a.attack_power」という処理が行われます。

これを実行する際には,各処理が行われる場所について,例えば以下のような物が考えられます。

  • ステータス管理も攻撃処理もサーバで行う
    1. Aからサーバに,攻撃したことを報告する
    2. サーバから全体に,新たなBのHPを報告する
  • ステータス管理は各個人で行い,攻撃処理は被攻撃者が行う
    1. Aからサーバに,攻撃したことと,その攻撃力を報告する
    2. サーバからBに,攻撃を受けたことと,その攻撃力を報告する
    3. Bは,減算後のHPをサーバに報告する
  • ステータス管理は各個人で行い,攻撃処理は攻撃者が行う
    1. Aから全体に,Bの新たなHPを報告する

これらは全て,システム全体では「b.hp -= a.attack_power」をしているだけです。 しかし,その評価を行う場所を変化させるためだけに,必要なインターフェースが大きく変化しています。


例えば,ある処理をサーバーで行うことを S[_] で表してみます。 同様に,Aで行う場合には A[_],Bで行う時には B[_] で表します。

例えば,Aの世界でだけBのHPを10減らすには,A[b.hp -= 10] と表記します。

先ほどの3つの例をこの表記で書き直してみると,それぞれ以下のようになりそうです。

  • A[ S[b.hp -= a.attack_power] ]
  • A[ B[b.hp -= A[a.attack_power]] ]
  • A[ b.hp -= a.attack_power ]

ここで生じる疑問として,3つ目の処理において「Aから全体に通知」といった処理が飛ばされているように見えます。 しかし,システム全体の話をしているのですから,計算結果は即座に通知されて当然に見えます。

そこで,各変数の単位(誰に対して定義されるか)と,その所有者(変数の問い合わせ先)を定義する必要があります。

この二つは同じものに見えるかもしれませんが,例えば「クライアントの人数分,サーバ側で保持する変数」という物も考えられますね。 (もしかしたらこのケースについては Node→_ なる値を確保することで解決するかもしれませんが,その場合には書き込み権限やロック周りが複雑になる気がします…… )


では,ここまでを纏めて,疑似的なコードを書いてみます。

Network {
  S -> { A, B }
}

struct Player {
  int hp;
  int atk;
}

ケース1: ステータス管理も攻撃処理もサーバで行う

S[
  [A] Player a;
  [B] Player b;
]

A[
  when A.attack {
    S[ b.hp -= a.atk ];
  }
]

ケース2: ステータス管理は各個人で行い,攻撃処理は被攻撃者が行う

A[ Player a; ]
B[ Player b; ]

A[
  when A.attack {
    B[ b.hp -= a.atk ];
  }
]

ケース3: ステータス管理は各個人で行い,攻撃処理は攻撃者が行う

A[ Player a; ]
B[ Player b; ]

A[
  when A.attack {
    b.hp -= a.atk
  }
]

表記の見た目は非本質ですので,直感で書いています。 そのうちちゃんと考えますね。


次回は,システムを後から拡張する場合について考えてみようと思います。

ではまた。