連鎖性言語を作る5

ループで悩んでいたのだが、とりあえずなるべく簡単に実装するということで、こんな感じにした。

      :loop => ->{quot=@stack.pop;loop{self.run(quot);if @break_flg then @break_flg=false;break;end}},
      :break => ->{@break_flg=true},

VMはこんな感じ。

  def run(ast)
    ast.each do |d|
      if Symbol === d
        @words[d].call
      else
        @stack << d
      end
      break if @break_flg
    end
  end

これでこのようなテストが通るようにはなった。

[ 5 ] [ 0 [ dup 5 < [ 1 + ] [ break ] if ] loop ] unit-test

んで、whileを作ってみようと思ったところで意外に難しくて止まった。
基本機能と実力の不足だと考えられるのでしばらくはそのあたりに注力するということで、今回は今まで書いてないことを色々と。

JoyとFactor

ForthにはもともとQuotationは無かったのだが、Joyという言語がForthを純粋関数型にしたような言語でそこで導入された。Quotationを扱うとそれを使った基本機能がいろいろと必要になるのでコンビネータとして実装された。ただ、Forthが実用言語だとするとJoyは理想言語みたいな感じで使いにくい、ということで、実用側に振ったFactorが出たわけだな。Factorが「A PRACTICAL STACK LANGUAGE」とされているのもそういう感じ。FactorにもQuotationがあり、各種コンビネータが実装されている。ちなみにオレ言語にはコンビネータがまだない。そのへんも作らねば。

スタックの限界

限界つってもプログラマの限界。スタック指向の偉い人によれば、スタックに積んで処理する中で人間が把握できるのはせいぜい4つぐらいまでなんだそうだ。それを超えると急激に難しくなる。スタックに積んだデータはトップから順に取り出すことになるが、使う順番がその通りでないことは多いし、後でまた使うということも多い。そういう場合はスタックの順番を入れ替えたり(swap)、コピーしたり(dup、over)してごにょごにょすることになる。このときに現在の状況をプログラマが記憶していないとコードが書けないのだが、それが難しいわけだな。Quotationを扱うと、Quotationそのものと、それが使うスタックも必要になるから、積む量は必然的に多くなる。基本ワードやコンビネータを使っても4個とかすぐ超える。なのでワードを細かく定義していって処理を分割しまくる必要がある。

関数型

レキシカルスコープと第1級関数を持つ言語はクロージャが作れる。Forth系言語はダイナミックスコープなので作れない。JoyやFactorもQuotationはあってもダイナミックスコープなのでクロージャは無い。てかそもそも変数が無い。このへんはオレ言語も同様になる。

カリー化

Factorにはcurryというワードがあって、

[ [ 1 1 + ] ] [ 1 [ 1 + ] curry ] unit-test

みたいな感じでスタックトップにQuotation、2番目に何かを置いて実行すると2番目の何かがQuotationに取り込まれる。これは便利なんだけど部分適用であってカリー化ではないような気がする。いや、

1 [ + ] curry

というのは「[ + ]という2引数受ける関数を、1引数受けて1引数関数を返す関数に変換してから呼び出す」、というふうに考えればカリー化&呼び出しという機能と考えることができそうだ。しかしそもそもモノが違うので同じ概念を適用しようとするのが間違ってるのかもしれない。

コンビネータとRetainStack

よく使うのがdip

: dip ( x quot -- x ) swap >r call r> ;

スタックの2番目をRetainStackに保存して、Quotationを実行してから戻す。通常のスタックをDataStackと呼び、それとは別に一時保存用スタックをRetainStackと呼ぶ。オレ言語にはまだない。>rとr>で出し入れする。他のコンビネータとしてはおなじみのeachやmap、filterなどなど、Factorにはあるがオレ言語には無い。

シンボル

オレ言語ではAST上のシンボルは実行されるワードとして扱う。となるとRubyのシンボルをスタックに積むようなことをするために別の文法が必要になる。例えば:を使う。Rubyだからな。

[ test ] [ :test ] unit-test

こんな感じ?あとQuotationにpushしたりpopしたりshiftしたりunshiftしたりできれば実行時にQuotationを生成することもできるようになりそうだ。これがあればdefine-wordも文字列じゃなくてシンボルを置けるようになって少しすっきりする。でもAST上では文字列もシンボルも意味を持たせてしまっているのでこれをどう表現するかで悩む。

Rubyの機能呼び出し

Rubyのメソッドは呼び出し側でメソッドが要求する引数の数を知ることができない。従って、スタック上にレシーバとメソッド名を置いても、そこからいくつのスタックを引数として渡すのかをいちいち指定してやる必要がある。例えばsendというワードを作ったときに、2send、3sendという形で引数の数をワード名に入れたものも定義していけば省略が可能だが、Quotationに詰めて渡すという手もある。Rubyの定数名からオブジェクトを取得する機能も必要か。
そのへんができたらDXRubyの機能を呼び出してゲーム作ったりとかもできるようになるのかね。Factorって最初はゲーム用スクリプト言語だったらしいし。DirectXOpenGLバインダもあるしな。

つまり

ようするにまだ何もできていない。ということだ。基本的なエラーチェックとかもないし、そもそも名前が無いし、もうちょっと形になってほしい。手軽に試せるようにirbみたいなやつも欲しい。
まあ、まだまだ先だな。