2010年4月2日金曜日

はつねなLispのようなものを考え中

今、片手間にLispのようなプログラミング言語を作っている。プログラミング言語というよりは、処理をデータで表現したかっただけなのだが。

何故作ったし
みくった〜♪を柔軟に拡張できるようにするため

文法
S式とM式を合わせたような文法を採用。略してSMs(ry
だけど見た目はあんまりLispっぽくない。これは、できればプログラマでなくても多少使えるように、などいろいろ考えがある。以下に、みくった〜♪のフレンドタイムラインプラグインのコードを示す。

tl = gen-timeline[200]
regist-window[tl]
onupdate << { (watch msg)
add[tl msg]
}

Lispなんて言わなければ良かった(笑)S式だけで書くと以下のようになるだろう。

(setq tl (gen_timeline 200))
(regist-window tl)
(add onupdate (lambda (watch msg) (add tl msg)))

まず、特記事項はM式だ。これは、S[...]と表記し、(S ...)というリストに展開される。一般的な言語の関数呼び出しf(x)に近い書き方ができるし、S式に慣れていなくても比較的コードが見やすくなる。
次に、演算子。通常Lispでは計算式を次のように書く。

(+ 1 2 3)

いわゆる前置記法でとっつきにくい。演算子がリストの最初以外に現れた場合、その前後の値を括弧で括り、第一要素と演算子を入れ替える。文章では説明しづらいが、要するにパース時に要素の入れ替えが発生するというものだ。
例えば、

1 + 2 + 3
A B + C D
1 + 2 * 3 - 4

は、

(+(+ 1 2) 3)
(A (+ B C) D)
(+ 1 (- (*2 3) 4))

となる。
最後に中括弧だが、これはlambdaの省略記法。

(lambda (a b) (+ (* a a) (sqrt b)))

は、

{(a b) a * a + sqrt[b] }

と書ける。また、シンボル_は、その関数が呼ばれた時の引数リスト、_nのように数字をつければ、n個目の引数に束縛されている。普通の用途ならこれは不要かと思ったが、フィルタを書くために多用することになるので、あえて用意した。

フィルタ関数

では、本題のフィルタにうつる。たとえば、幾つかの優秀なツイッタークライアントでは、「ユーザaか、aへのリプライか、googleが含まれている」つぶやきだけを抽出したタブを作成できるが、これはこう書ける。

{ ()
or{
(assoc[_0 'user] == "a")
include?[assoc[_o 'message] "@a"]
include?[assoc[_o 'message] "google"]
}
}

(※注:orは引数をtが帰ってくるまで順次実行するマクロだが、はつねLispでは表記上の理由からlambdaも渡せる)
ちょっと複雑だ。Rubyだとこうなるだろう。

lambda{ |m|
m[:user] == 'a' or
m[:message].include?("@a") or
m[:message].include?("google")
}

あれ、かわんない。無理もない。はつねLispの関数は、以下の値のいずれかになる。
  1. =演算子でオーバライドされた関数
  2. はつねLispで定義されたプリミティブ関数
  3. 第一引数のクラスのメソッド
  4. 同名のRuby関数
例えばchomp[string]は、Rubyでstring.chompと書くのと同じ意味だ。しかしながら定数にはアクセス出来ないので、例えばnew[Time]のようなことはできない。これはあえて入れた制約で、これによって例えばはつねLispをJavaScriptなどで再実装する場合に、String, Numeric, Listくらいを再実装するだけではつねLispがすべて動かせるようになる。
また、はつねLispはほぼRubyに直訳出来る。プリミティブ関数も短いRubyコードだし、何よりほかのメソッド呼び出しは全てRubyのそれだからだ。つまり、高速化のためにRubyのコードに翻訳することが出来るのだ。

いわば、マクロを使えるようにしたRuby、という感じなのかもしれない。当然、Rubyとちがって全てS式にできるというのが最大のメリットだが。