読者です 読者をやめる 読者になる 読者になる

プログラミング言語 Shiba (仮称)

プログラミング

こんなプログラミング言語を考えている。

Shiba

fact: {
  @ if ( = 0 ) :x { 1 } { * :x fact ( - 1 ) :x }
  x:
}

fact 10

even: { @ if ( = 0 ) :x { true } { odd ( - 1 ) :x } x: }
odd: { @ if ( = 0 ) :x { false } { even ( - 1 ) :x } x: }

even 42
even 99

こんな感じ。(上のコードはまだ動かないけど。)

まだ色々決まっていなくて、細部はいくらでも変更するつもりなのだけれども、どういうアイディアかを少し書きたい。

連結型言語

以前からForthとかPostScriptのような連結型言語 concatenative language のアイディアをあたためてきたのだけれども、KittenやOmのような言語があることを知ってしまったので、これは自分でも作らないといけないだろうと思い作ることにした。

スタックと関数の合成

スタックを使った言語には、関数の合成がしやすいという利点がある。

例えば割ったあまりが0である、つまり倍数を判定する関数を書こうとする。Haskellなら、もちろんラムダ式を使って

\x y -> x `mod` y == 0

と書けばいいのだけれども、この程度の処理だったらポイントフリースタイルで書けてもいいはずだ。しかしHaskellでこれをポイントフリーで書こうとすると意外と厄介で、Pointfree - HaskellWikiに書いてあるテクニックを駆使しないといけない。引数が1つの場合、2つの場合、それ以上の場合とそれぞれで異なるコンビネーターを使わないといけないのだ。

スタックという共通のデータ配線用の仕組みを導入すれば、複雑なコンビネーターの使い方を覚える必要はない。先ほどの処理は、Kittenではこう書ける。

{ % 0 = }

語順

Kittenの語順は左から右へ実行されるような語順(逆ポーランド記法)になっている。これはForthと同様だ。

Omという言語はスタックベースではなく、オペレーターを左に、オペランドを右に置き、オペレーターとオペランドが接する真ん中で処理が行われるようになっている。オペレーターは、右から左へ実行される語順(ポーランド記法)になっている。

図式順記法について少し - 檜山正幸のキマイラ飼育記にならえば、Kittenは図式順で、Omは反図式順になっているわけだ。Haskellの場合は、関数適用は反図式順だが、モナドのdo記法などは図式順だ。

Shibaでは、あえて反図式順にする。理由はOmがポーランド記法を採用している理由とだいたい同じで、処理する内容が先に見えていたほうが、どこまでが処理のひとくぎりかがハッキリするからだ。日本語のような語順だと、どんどん言葉を繋いで結論をだらだらと引き伸ばすことが出来てしまう。言葉は未来に向けては継げるが、過去に継ぐことは出来ない。記述を時間の流れとあえて逆にすることで、処理が無限に延期されるのを防ぐわけだ。

変数

関数をパイプのようにイメージするなら、適用型言語の書き方は変数という「パイプの断面」が見えてしまっている書き方だ。連結型言語の書き方は、スタックを介してパイプどうしが連結されている。

パイプの断面が見えたほうがよい場面もある。具体的には、インターフェースが異なるパイプどうしを繋げる「コネクター部」を作る場合で、本質的にデータの流れが複雑に絡み合うようなものになるから、このような処理を書くときには変数やパターンマッチを使った書き方が向いている。Kittenに変数を使う構文があるので、Shibaにも取り入れたが、書き方がKittenとは異なっている。

dup: { :x :x x: }
swap: { :x :y x: y: }

swap 3 dup 4

x: はスタックの値を取り出して変数に代入する。 :x は変数の値をスタックに積む。この操作は対称的なので、記法も対称的にしてある。数値をスタックに積むのも:1とかいう記法にしようか検討したけどさすがに煩雑なので、リテラルは普通に書けばスタックに積むという操作になっている。また、単にxと書けば変数xに束縛されている関数を指すので、Kittenにあるdefのような予約語は必要なくなっている。

変数のスコープは検討中だ。現在の実装では多分動的スコープになってしまっているのでそれは修正したいのだけれども、レキシカルスコープにもしたくない。関数の外の識別子を参照するというのは、先ほどのパイプの話で言えば、関数の外側へ向かってパイプが伸びていることになってしまう。副作用がないとしたところで、嫌なデータの流れがあることになってしまうので、関数の外側を参照できなくしてもいいかもしれない。そうすると、再帰の書き方には工夫が必要になる。

even: { @ if ( = 0 ) :x { true } { odd  :even :odd ( - 1 ) :x } x: odd: even: }
odd: { @ if ( = 0 ) :x { false } { even :even :odd ( - 1 ) :x } x: odd: even: }

even :even :odd 42
even :even :odd 99

こんなふうになるのだろうか? (このコードはまだ動作しない。) そもそもこんな書き方をすると再帰型みたいなの使わないと型が付かないんじゃないか。うーん。