Scheme の手続きを動的に

やりたいことはすごい簡単なんだけど、やり方がわからない。

(define op #(+ - * /))
(define calc
  (lambda (kind lhs rhs)
    ((vector-ref op kind) lhs rhs)))
(calc 0 1 2)

手続きをベクタとかリストに持っておいて、それを取り出して実行したい。

Gauche だと以下のようなエラーメッセージ。

gosh> (calc 0 1 2)
*** ERROR: invalid application: (+ 1 2)
Stack Trace:

期待通りに見えるんだけど、駄目。

Racket だと以下のようなエラーメッセージ。

> (calc 0 1 2)
procedure application: expected procedure, given: '+; arguments were: 1 2

同じようなメッセージ。若干 '+ というのが気になるが、クオートされてるわけではないはず。

原因を特定するために、単純化したものを書いてみる。

((car (list +)) 1 2) ; OK
((vector-ref (vector +) 0) 1 2) ; OK
((car '(+)) 1 2) ; NG
((vector-ref #(+) 0) 1 2) ; NG

ここで少し原因に近づいた。

(list +) はよくて '(+) は駄目、(vector +) はよくて #(+) は駄目だということ。そもそも、ここでは #(+) なのか '#(+) なのかもよくわかってない。

R5RS によると、#(0 (2 2 2 2) "Anna") はベクタの外部表現で、ベクタ定数は '#(0 (2 2 2 2) "Anna") というようにクオートしなければならないと書かれているが・・・。

僕の認識では、クオートが必要なのは、その S 式が評価されると困る場合であって、つまり '(+) はクオートがないと足し算で 0 と評価されてしまうため必要だけど、#(+) は評価される心配がないので、クオートはいらない。


最初の例は、以下のように書けばできるというのはわかったんだけど、何で #(+ - * /) は駄目で (vector + - * /) は駄目じゃないのか、その理屈を知りたい。

(define op (vector + - * /))
(define calc
  (lambda (kind lhs rhs)
    ((vector-ref op kind) lhs rhs)))
(calc 0 3 4)