オブジェクト指向と並列論理型言語

関数型言語オブジェクト指向の関係について一部で話題のようですが(第4回 関数型言語とオブジェクト指向,およびOCamlの"O"について | 日経 xTECH(クロステック))、並列論理型言語でもプログラミングテクニックを駆使して、オブジェクト指向っぽいプログラムが書けます。と思って説明しようとすると、文法から説明する必要が… まぁ、KLIC講習会テキスト(http://www.klic.org/software/klic/lang/node2.html)を読んでもらうという手もありますが、頑張って説明してみましょう。文法はKL1で。Prolog使いの人はbacktrackのことは忘れてください :-)

まず、リストの総和を求めるプログラムを上記の講習会テキストから引用します:

sum([One|Rest],PSum,Sum) :- NewPSum:=PSum+One, sum(Rest,NewPSum,Sum).
sum([],PSum,Sum) :- Sum=PSum.

といっても、訳がわかりませんね、はい。まず、KL1ではリストは[A|B]という感じで表します。AがCarでBがCdr。は終端を表します。リストは[1,2,3]のようにも表せます。[1,2,3]は[1|[2,3] ]と等価です。

":-"の左側で、関数定義の仮引数を書いていて、右側で実行する内容を書いていると思ってください。普通の関数定義と同じですね。

ただ違う点がいくつか。まず、右側の","で区切られた部分はすべて並列に実行されます。変数は単一代入なので、どんな順番で実行しても矛盾は起こりません。

それから、":-"の左側の仮引数の内容で、値の待ち合わせと分岐が表現できます。上記の例だと、"sum([One|Rest],PSum,Sum)"と"sum(,PSum,Sum)"の2つの定義があるわけですが、第1引数がリストか、終端かで分岐しています。まだどちらとも決まっていない場合は決まるまで停止します。

それではプログラムを読んでみましょう。呼び出し時は、sum([1,2,3],0,Sum)のように呼び出すと、Sumに6が返ってくることを期待します。第2引数は現在までの和を保持するテンポラリの変数です。

このように呼び出すと、第1引数はリストですから、1行目の定義が選択されます。1行目の定義は

sum([One|Rest],PSum,Sum) :- NewPSum:=PSum+One, sum(Rest,NewPSum,Sum).

リストのCarを"One"という変数にとって、"NewPSum:=PSum+One"で現在の総和とOneを足したものをNewPSumに入れています。CdrはRestにとっています。そして、Restを第1引数、NewPSumを第2引数にして、"sum(Rest,NewPSum,Sum)"としてsumを再帰呼び出ししています。

再帰呼び出しが進み、リストがなくなると、2行目の定義が選択されます。

sum([],PSum,Sum) :- Sum=PSum.

これで、現在の総和を第3引数にセットし、最終結果がSumに返されます。

というように、リストの総和を求めるのですが、さて、オブジェクト指向風のプログラムはどう書くか。

myobject([getvalue(A)|Rest],CurrentValue) :- A = CurrentValue, myobject(Rest,CurrentValue).
myobject([setvalue(X)|Rest],CurrentValue) :- myobject(Rest,X).
myobject([],CurrentValue).

これは、実はオブジェクトの定義です。getvalue(A)で現在の値を取り出し、setvalue(X)で現在の値をXにします。
ここで、getvalue(A)などは、KL1の構造データの表現で、"getvalue"の部分は値が決まっていて、"A"の部分は変数という扱いになります。

使い方は、

foo(V):-
  myobject(Messages,0),
  Messages = [setvalue(1),getvalue(V)].

という感じ。Vには1が返ります。

少し説明すると、myobjectの第1引数のリストには、オブジェクトのユーザからのメッセージが入ります。第2引数はオブジェクトの状態が入っています。
myobjectの第1引数のリストのCarがgetvalue(A)の場合、第2引数の値をAにセットして、残りのリストを第1引数に、元々の第2引数をそのまま第2引数にして、myobjectを再帰呼び出しします。
myobjectの第1引数のリストのCarがsetvalue(X)の場合、残りのリストを第1引数に、Xを第2引数としてmyobjectを再帰呼び出しします。
リストが終端していると終了します。

オブジェクトのユーザは、オブジェクトの第1引数のリストにメッセージを入れることで、オブジェクトを利用できます。このように、単一代入変数の世界でもテクニックを使うことでオブジェクト指向風のプログラムが書けるのでした。ちょっとトリッキーだけど。

オブジェクトのユーザが複数いる場合は、リストをマージしたりするのですが、長くなったのでこの辺で。

関数型言語ではこういうテクニックは使えないんだろうか? 型推論ではまる?