プロローグ
Lisp系言語はカッコが多いです。LispのソースコードはLispのデータである(同図像性)ということを一目見て実感します。いやはや、うっとりしますね。ずっと眺めていたい。とはいえ、慣れていない人間がその意味を追うためにソースコードを見たり書いたりする際に、スッと入ってこないことがあることがあるのも事実。Clojureをたまに書いたりする自分ですら、エディタのおかげで内容を読み取るのに困ることはないものの、S式そのものに不満がないわけでもありません。
そんな時に下記の記事を見ました。古い記事ですが「やっぱりそうだよね」としきりに頷いてしまいました。
https://practical-scheme.net/wiliki/wiliki.cgi?Lisp%3AS式の理由
以下、よく話題になる部分も含め、気になる部分を挙げてみます。
関数適用されるリストとデータとしてのリストがほぼ同じ外見
; b,c,dには数値が入っている
(sum b c d) ;関数適用
'(b c d) ;quoteされたリスト
(list b c d) ;関数適用だけど結果的にはリスト
何を今更、と思われるでしょうが、quoteはあるものの、知らない人にはやっぱりわかりにくいのでは。C言語の流れを汲む文法を持つ言語などでは、関数はカッコの外に出ていて、引数だけがまとめられます。個人的には、意味が違うものは違う見かけになってほしい気もします。「同図像性を保つには、これこそが最善」という声も頭の中で響くのですが、もうちょっとなんとかならんのかい、とも感じる自分もどこかにいます。
その点、Clojureは偉い。
(list b c d) ;リスト
[b c d] ;ベクタ
{:e "f" :g "h"} ;マップ
カッコの種類をフル活用しているおかげで、めちゃ読みやすいです。関数適用が行われるのはリストの時のみですし、最初見た時は軽く感動しました。もうこれでええんや!俺Lispとか作らんでもええ!
……としばらくは思っていたのですが、やっぱり、関数と引数が同じリストの中にいることがどうしても気持ち悪い。何をやっても所詮S式なのですが、「構文」まではいかない、S式の他の見かけは、考えられないんだろうか。もちろん、マクロを書く時に困らないほどにはS式の力を保ったままで(この点Elixirのマクロは惜しいと勝手に考えています)。
まず、「リストの最初の引数は関数、残りは引数、評価すると値が返却される」という大前提と戦わないといけない。「リストは評価すると関数が動きだす」のがデフォルトとも言えます。このデフォルトを、Clojureのベクタのような「評価してもデフォルトでは最初の引数が関数とみなされない」側に倒してみるとどうでしょう。
[1 2 3] ;=> 評価しても[1 2 3]のまま
従いまして、ふつーのリストだと関数適用っぽいものも、前提が変わると結果が変わります。
[sum 1 2 3] ;=> sumが変数扱いになるけれど、sum関数が走るわけではない
話を簡単にするためにキーワードに。
[:sum 1 2 3] ;=> 評価しても[:sum 1 2 3]
さて、ここからです。このベクタを外から関数適用したいのです。つまりapplyしたい。
仮に書くと、こんな感じでしょうか。
[:apply' [:sum 1 2 3]]
まあ、でもこれだと、評価してもapplyは走りません。だって、そういう前提で今考えているので。評価しても、データ構造はそのままです。だから、何か「apply的なことをする」、特別なオペレータを導入する必要があります。
いろんな方法があると思うのですが、私の頭の中には、Javaのアノテーションに似たものが浮かんでいました。例えばこんな感じで。
@apply [:sum 1 2 3]
もはやClojureではなくなったので、この角カッコでできたFormを仮にシーケンスと呼びますが、このようなアノテーションをシーケンスに付与することによって、データ構造に関数を適用できるルールにしよう、というアイディアです。そしてこのアノテーションは、概念上、シーケンスに属しています。言い換えると、必ず後続にシーケンスを必要とするということです。
つまり、「フォーム=リスト」と見做すのではなく、「シーケンスとアノテーション」という二つの基礎概念から成り立っていると見做すわけです。そして、applyアノテーションがシーケンスに付与されている時にのみ、関数適用が行われるルールにします。
関数と引数を分ける
ここからは、構文変換を行うことで構文らしきものを作っていきます。きっとリードマクロを使うことでしょう。(上記アノテーションもリードマクロで作れますけど、概念上の話ということで)
sum(1, 2, 3);
みたいにしたいので、迷いましたがドット(.)を構文に採用することにしました。
.sum[1 2 3] ;これをマクロ展開すると
@apply [:sum 1 2 3] ;こうなるイメージ 評価すると6になる
C言語っぽい見かけにしたいので詰めて書いてますがトークン的には
. sum [1 2 3] ;スペースを入れた
と分解されます。
(sum 1 2 (sum 4 5 6) (- 8 5)) ;他のlispだとこう
.sum[1 2 .sum[4 5 6] `[8 - 5]] ;新しい構文だとこう
これだけだと上の方がわかりやすいやん、みたいに感じてしまうかもしれません(実際私もそう感じました)が、コード量が増えてきて、いろんな構文を導入し始めると、だんだんその考えも変わってきました。なによりlispに馴染みのない人は下の方が意味を取りやすいはず。
密かにバッククオートとか出てきてますが、これで中値記法ができるつもり。
次回予告
自己満足全開、拙い垂れ流しの文章となりましたが、発展させて色々と考えました。力尽きたので続きはまたの機会に。S式への不満その2は「末尾の閉じカッコ弾幕」です。例は我らがバイブルOn Lispより。
(defun revc (x k)
(if (null x)
(funcall k nil)
(revc (cdr x)
#'(lambda (w)
(funcall k (append w (list (car x)))))))) ;この末尾の閉じカッコ弾幕
Lisperはカッコを気にするな、インデントで読むんだ、エディタに任せておけばいいんだ、と言います。事実、私もそうやっております。それでも、この拭い切れないもやもや。カッコの数を、ちょうどよい塩梅で減らすにはどうすればいいんでしょう??
というテーマでいずれ書こうと思います。お付き合いいただきありがとうございました。ほなまた。