前回示したような Grass の長いプログラムを如何にして「書く」か、という話。
Grass のある程度長いプログラム(Wとwとvの列)を「直接」書ける強者は恐らく存在しないと思われ、実際には「もっと書きやすい形式」からの変換によってつくられるのが専らである。よく用いられるのが grass.el という Emacs 上で動く開発環境である(S 式形式のプログラム記述から変換する:リンク先の真ん中の画像を参照)。しかし、生憎私は Emacs 屋でない。仕方がないので自分で「書きやすい形式」とそれからの変換器を Lua で作ることにした。*1
この中の planter.lua を用意して、以下のような Lua プログラムを作る。これを実行すると、前回掲載した Grass のプログラムソース(ただし改行はない)が出力される。
ん……? どうして Lua の関数定義の本体部分に Lua でも Grass でもない謎言語が書かれているのかって? いや、よく見てみよう。これは全く以て Lua のコードである……構文上は。
このコードの意味は次のように解釈する。例えば、先頭の
Cons = _.a.b.f (f(a)(b))
val Cons = fn a b f => f(a)(b)
を表す。*2さらに、プログラムの中程にある、「現在のカウンタ値がアホであるか」を判定する関数 IsAho の定義である
IsAho = _.c0.c3.dgt1.dgt2.dgt3.dgt4.cycl [is3p1 < c3(dgt1)] [t < c3(dgt2)] [is3p2 < t(t)(is3p1)] [t < c3(dgt3)] [is3p3 < t(t)(is3p2)] [t < c3(dgt4)] [is3p4 < t(t)(is3p3)] [t < cycl(c0)] (t(t)(is3p4))
の中にある [変数 < 式]
は let 式を表す。すなわち、次のように解釈される。
val IsAho = fn c0 c3 dgt1 dgt2 dgt3 dgt4 cycl => let is3p1 = c3(dgt1) in let t = c3(dgt2) in let is3p2 = t(t)(is3p1) in let t = c3(dgt3) in let is3p3 = t(t)(is3p2) in let t = c3(dgt4) in let is3p4 = t(t)(is3p3) in let t = cycl(c0) in t(t)(is3p4)
その他の事項。
- Abs の外で App を行うには
True = _(C(False))
のように書く。右辺を_( )
で囲むことに注意。 I = _.x ()
やOut2 = _.x [Out(Out(x))] ()
のように本体部の式が空の場合は、その本体部を処理する時のスタックトップの値が関数適用の値になる。つまり前者は意味的にはI = _.x (x)
と同じ(ただし、適用を全く含まない単純な変数だけの式は実際には許されない)。後者は_.x (Out(Out(x))
と同値になるが、「Lua の構文規則上(...)
の直前で改行することが許されない」という制限があるので、その場合に該当の部分を[...]()
と書き直して回避する([ ]
の前は改行可能)という使い方ができる。上の例で挙げたIsAho
も実際には「本体部が空」の書き方をしている。- 特殊な変数
_E[n]
は「その時点のスタックのインデックス n を用いる」(Grass の抽象文法の App の引数の直接指定)を意味する。
*「俺の知ってる Lua と違う……」
ZR「世の中たまには変なことが起こるものなんだよ……」