川合さんのエッセイ「Schemer's way」は面白く、とてもためになります。
ただ一点、フェアな議論なのか気になるところがありました。
多くの言語では、新しいオペレータを追加するのは言語そのものを拡張しなければならないだろう。例えばJavaを拡張してPerlの `x' オペレータを実装するには、構文規則から書き直さねばならない。既存のオペレータに新しい意味を追加するというメカニズムは、これもいくつかの言語では言語そのものに組み込みの機能となっている。
Lisp系の言語では、オペレータと関数呼び出しとの間に区別が無いため、オペレータの機能の拡張ということは議論にならない。新しいオペレータが欲しければ、あるいは既存のオペレータを拡張したければ、いつでも自分で書けるからだ。
Java には構文規則を変えなければいけない例を挙げているのに、Lisp では構文規則を変えなくてよい例を出しています。
僕も Lisper なので Lisp の肩を持ちたいところですが、Lisp だって前置構文の範疇を越えようと思うと、構文規則を変えないといけないのではないでしょうか?
ハッシュの演算子
たとえば、ハッシュ(hash)をキー(key)で検索した結果の値を知りたいとしましょう。JavaScript では、その演算子を "." で表します。
hash.key
Lisp でこういう表現は、リードマクロを使っても、マクロを使っても無理でしょう。それで、括弧を使ってみることにします。
(hash . key)
これだと単なるドット対になるので、演算子を => で表してみます。
(hash => key)
これも無理ですね。Lisp でできるのは、演算子を一番前に置いた形になります。
(=> hash key)
結局、ハッシュを検索する関数に => という別の名前を付けただけですね。
Paul Grahamさんの答え
Arcがリリースされたので、さっそくインストールしてみました。しかし、Leopard では動きませんでした。orz
% mzscheme -m -f as.scm compile: bad syntax; function application is not allowed, because no #%app syntax transformer is bound in: (quote nil)
しょうがないのでチュートリアルを読んでいると、ハッシュがこういう風に使えると書いてあり、衝撃を受けました。
(= airports (table)) (= (airports "Boston") 'bos) (airports "Boston") ;; => bos
演算子がありません! なぜこんなことができるのでしょうか? さっそく調べてみました。
table は、単に Scheme のハッシュを作る関数でした。
(xdef 'table (lambda () (make-hash-table 'equal)))
xdef の定義とかも調べたのですが、魔法があるようには思えません。さんざんハックして、ついに見つけました。
(define (ar-apply fn args) (cond ((procedure? fn) (apply fn args)) ((pair? fn) (list-ref fn (car args))) ((string? fn) (string-ref fn (car args))) ((hash-table? fn) (ar-nill (hash-table-get fn (car args) #f))) (#t (err "Function call on inappropriate object" fn args))))
Arc の Read-Eval-Print ループの eval 使われる ac-apply で、ハッシュであれば hash-table-get を呼ぶようになっているのです。
eval と apply の関係が分らない人は、Emacs Lisp でピュアな Lispを読んで下さい。