言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

スタック言語の型推論についてだらだら考える。

今日考えた事を忘れないようにメモする。Joy 言語を一生懸命やっている理由の一つは、プログラミングの可視化にものすごい影響があると思っているから。Joy 言語の簡潔さは驚異的だ。例えば x * (y + z) と言った式が、+ * で済んでしまう。簡潔さは無駄な情報が無いからで、無駄な情報が無い事で本質的な情報が浮かび上がる。

以前未踏で「言語ゲーム」というプログラムを作っていた時に、プログラムの意味はプログラムに全て書かれているから、構文木を画面に表示すればそれが意味を可視化した事になると無邪気に考えていた。しかしそれは完全に違った。構文木は字面に過ぎない。例えばある変数の情報がどうプログラムに流れるのか構文木から知る事は出来ない。

型推論モナドを知った時に、これはもしかして可視化に使えるのかも知れないと思っていたが、僕の馬鹿な頭ではイマイチ応用が出来なかった。そこで Joy を教えてもらった。Joy は変数を使わず、情報はスタックを使って受け渡しする。変数が無いから情報の流れを変えようと思ったら swap や dup なんかを使って明示的に示さなければならない。という事は、データの流れがプログラムにそのまま書いてある。これは凄い!

そこでこのデータの流れをそのまま graphviz を使ってツリーで表現する事を考えた。これはどういう事かと言うと、+ * のようなプログラムから、数字がどうプログラムを流れるのかを表現するデータフロー図を作る事。+ * くらいなら x * (y + z) の構文木を描いたほうが簡単だが、再帰呼び出しのような複雑な事を図示させようと思うと構文木では全く歯が立たない。

x   y   z
 \   \ /
  \   +
   \ /
    *
    |
  こたえ

とりあえず + * を表現するのに枝が何本必要なんだ?という事から考えた。見ての通り入口が三本出口が一本だけど、これはどうやって計算するんだろう。これを知るにはそれぞれの演算子の入口と出口の数を示す必要があるが、こういう記法を採用する。

関数 <入口, 出口>
* <2, 1>
+ <2, 1>
+ * <?, ?>

掛け算や足し算の入口が 2 で出口が 1 の時、二つを組み合わせると全体で出口と入口は幾つあるだろうか?これはなかなか面白いクイズなので、この後を読む前に自分で考えると良いと思う。

ポイントは、前の足し算と後ろの掛け算は一本の線で繋がっているという事。この繋がり方は関数によって違ってくるが、 と置くと min(q1, p2) となる。そうすると全体では となる。+ * の場合だと <2 + 2 - 1, 1 + 1 - 1> = <3, 1> になる。

Joy の関数は結合的なので、関数が沢山並んでいてもこの計算を繰り返せば、プログラム全体で幾つスタックを消費して幾つ作るのかが分かってくる。ここまで分かったら後はグラフを描くのは明日やります。。

試しに他の例でもやってみる。

swap - <2, 2> <2, 1> = <2, 1> --- \x y -> (y - x)
dup +  <1, 2> <2, 1> = <1, 1> --- \x -> (x + x)

絶対こういうの他の人がやってるだろうと思ってググッたのですが、スタック言語の型推論というのは相当マニアックな話題らしく、うまい資料が見つかりませんでした。知ってたら教えてください。