久しぶりにCatyScriptとそのモデル(意味論)の話。([追記]実際にソフトウェアを動かしてみたい方はコチラを参照。[/追記])
ここのところずっとCaty上のアプリケーションを作る作業をしてまして、Caty本体の開発がサクサク進んでいるわけじゃないのですが、それでもジワジワとCatyの機能は増えています。
ごく最近、CatyScriptで変数が使えるようになりました。んじゃ今まで変数が使えなかったのか? はい、使えませんでしたよ。CatyScriptのモデルでは、そもそも変数概念があからさまに現れることはなく、変数なんて要らないのです。しかし、現実的な話として「変数なしプログラミング」は辛いです。そこでもう我慢ならんから入れよう、と(まだ実装は完全じゃないですが)。
で、今日の話は、「理屈の上では変数が要らない」、その理由です。これは、CatyScriptのモデル(意味論)を調べれば明らかになることです。CatyScriptのモデルはとある圏なのですが、組み込みの機能を定式化するだけでも、クリーネスター付きのインデックス付きデカルト半環作用圏(indexed cartesian semiringal effect category with Kleene-star)という、長たらしい形容詞が付いた圏となります*1。とりあえずは単なるデカルト圏で話をしますが、それでも事の本質は十分伝わります。
説明の道具にお絵描き圏論=絵算を使います。
内容:
- 射のプロファイルと結合
- 入力、出力、パラメータ
- 変数の生成と初期化
- 終端記号と変数の参照
- 変数ポイントとワイヤーストッパー
- 2つの例題
- 変数なんて要らないよ
射のプロファイルと結合
圏の射fと、域X、余域Yの組み合わせを f:X→Y と書きます。X→Y をfのプロファイルと呼びましょう。CatyScriptでは、コロンを2個 '::' 使って、矢印はアスキー文字2文字の組み合わせ '->' で表現します。次のようですね。
- f :: X -> Y
これに対応する絵は次のとおり。
2つの射 f, g の結合(合成;composition)は、f;g とか g・f とか書きますが、CatyScriptではパイプ記号を使って f | g とします。f :: X -> Y 、g :: Y -> Z のとき、結合 f | g のプロファイルは X -> Z となり、次のように図示できます。
なお、入力がデカルト圏の終対象のときは、左のワイヤーを省略することがあります。普通の言葉でいえば、「入力がvoidなら特に描かない」のです。
入力、出力、パラメータ
f :: X -> Y において、fをコマンド、Xを入力データの型、Yを出力データの型と解釈します。入力と出力以外に、パラメータと呼ぶデータがあります。パラメータのデータ型をPとして、パラメータ付きコマンドのプロファイルは次のように書きます。
- f P :: X -> Y
対応する絵は:
考えている圏はデカルト圏なので、f P :: X -> Y の代わりに f' :: P×X -> Y を使っても同じことです。パラメータという概念を入れているのは、次の理由からです。
パラメータがいやなら、オーバーロードされたコマンドを複数の異なるコマンドに分けて、パラメータを直積により入力に押しこんでしまえば、入力と出力だけの議論に還元できます。現実的には、パラメータは必須機能だと思いますけどね。
変数の生成と初期化
CatyScriptでは、リダイレクト記号 '>' を使って変数への“代入”を行います。f > x とすると、コマンド(圏の射)の出力を変数xに代入できます。標準出力をファイルにリダイレクトすることに似てますが、次の点が違います。
- x が既に存在する変数のとき、f > x は失敗する
- f > x をしても出力は失われず、出力をそのまま使うことができる。
一番目の制限は、要するに単一代入だということです。一度バインドした値は変更できません。つまり、変数の生成と初期化は同時に行われ、それ以降イミュータブルになります。
二番目は分かりにくい表現ですが、言ってる内容は単純です。OSのシェル、例えばbashだと、echo hello > foo > bar とか echo hello > foo | grep ello とかが期待通りに動きませんが、CatyScript では動くってことです。もっとも、何を期待するかによりますがね:
caty:root> "hello" > foo; %foo
"hello"
caty:root> "hello" > foo > bar; %bar
"hello"
caty:root> "hello" > foo > bar; [%foo, %bar]
[
"hello",
"hello"
]
caty:root>
OSシェルで言えば、'>' のところで必要に応じてteeコマンドを使っていると思えばいいのです。
$ echo hello > foo; cat foo
hello$ echo hello | tee foo > bar; cat bar
hello$ echo hello | tee foo > bar; (cat foo; cat bar)
hello
hello$
"hello" > foo > bar というリダイレクトの繰り返しは、bar = foo = "hello" という形の代入式とまったく同じです。
終端記号と変数の参照
上のCatyScriptの実行例(Caty対話型シェル)で既に出てきていますが、セミコロン(';')はパイプラインを終了させます。出力はセミコロンのところで捨てられて、それより先(右側)には渡されません。
生成初期化した変数を参照するにはパーセント記号('%')を使います。%foo と書けば、fooという名前の変数値を参照できます。構文的には、定数が書けるところならばどこにでも変数参照が書けます。変数参照が出現したら、その場所に直接データリテラルを書いたのと同じ扱いです。あるいは、%foo を cat foo のように思えばいいでしょう。
変数ポイントとワイヤーストッパー
さて、お絵描き圏論に戻りましょう。f > x という代入式は、次のような絵で表現します。
ワイヤー(線)はデータが流れる道筋の表現です。変数はそのワイヤー上の点(ポイント)に過ぎません。変数を表すワイヤー上の点を変数ポイントと呼びましょう。
f > x は、fから右に出るワイヤー上にxという名前の変数ポイントを作ります。変数ポイントが作られてもデータが失われることはありません。
パイプラインを終端する f ; は、fから右に出るワイヤーに、それ以上は繋げないようにストッパーを付けます。
上の絵で、fからの出力(右側のワイヤーを右に流れるデータ)は捨てられて、それ以上は利用できません。
2つの例題
今までの話で、次の記号に対応する絵の描き方を説明しました。
- '|' -- パイプ記号: 射(箱)をワイヤーで結合する
- '>' -- リダイレクト記号: ワイヤー上に変数ポイントを作る
- ';' -- セミコロン: ワイヤーにストッパーを付ける
- '%' -- パーセント記号: 変数参照、変数ポイントからワイヤーを引いて来る
少し複雑な例題をやってみましょう。
- f > x | g > y ; %x | h %y
絵の描き方のルールを思い出して、ジッと眺めれば分かると思いますよ。
- %t | f > x; g > y; {"a": %x, "b": %y} | h > z ; k %x %y %z
これはちょっと補足説明が必要ですね。
絵の中央付近にある閉じ中括弧をデカクした記号は、いくつかの入力からJSONオブジェクトデータを作る操作を表します。同様に、閉じ大括弧をデカクした記号は、いくつかの入力からJSON配列データを作る操作です。
コマンドkに3つの引数(位置パラメータ)が付いているのは、パラメータデータとして [%x, %y, %z] という配列が使われることなので、いったん長さ3の配列を作って、それをkの上から繋いでいます。箱の上側に、パラメータデータを入力する端子が付いているとしています。
変数なんて要らないよ
出来上がった絵から、変数ポイントに付いている名前を消し去っても絵のトポロジーは何も変わりません。変数ポイントの名前は、ワイヤリング(配線)の作業途中では目印に使いますが、変数の名前はデータフロー構造に何の影響もないのです。
実際、変数ポイントを使って描ける絵は、変数を使わずに描くことができます。そのとき、対角Δ、終射!、対称σなどの特殊な射を使いますが、これら特殊な射と一般の射の組み合わせで任意のデータフロー構造を組み立てることができます。絵じゃなくてテキスト表現のときでも、記号としてのΔ、!、σの組み合わせを使えば変数は不要です。
つまり、変数なんて要らないんですよ。
だけどね、冒頭で言ったように、「変数なしプログラミング」は辛いのよ。辛さに負けて変数入れました、とさ。
*1:[追記]http://twitter.com/#!/hiyama_on_caty/status/31866296186441728 によると、もっと長い形容詞が付いてましたね。inclusive で with generalized Kleisli extensions と。extensionが複数形なのは、実際にいくつかの拡張が付属しているからです。[/追記]