elispでローカル関数しよう!!
elispでは、ローカル関数はlambdaにしてapplyなり、funcallして明示的に呼ばないといけない。
ローカルな空間でdefunしても、その定義はグローバルにいっちゃうのである。
(defun func () (defun local (x) (* x x))) (func) ; -> local (local 3) ; -> 9 ;; Oh!
しかし、このlocalというのは、グローバルに定義されているようで実はグローバルに定義されているわけではないのである。
elispのシンボルはハッシュテーブルへのリンクを含んでいることは有名な話だろうか。(いや、有名ではないと思うが)
(setq symbol 'x) ; -> x (symbol) (eq 'x symbol) ; -> t (setq table (make-vector 41 nil)) ; make obarray (setq symbol (intern "x" table)) ; -> x (symbol) (eq 'x symbol) ; -> 同じ'xでもテーブルが違うのでnil
シンボルというのは「シンボルの名前」と「シンボルの値」と「シンボルの関数値」と「そのシンボルが属するテーブルへのリンク」からなるのである。
で、だったら、普通に"'hoge"とか書いたシンボルは、どのテーブルに属するシンボルなのか、という話なのだが、elispでは、これは、obarrayというテーブルに属する、ということが決まっている。
defun関数は引数で渡されたシンボルに関数を定義する、ということをやっているわけだが、普通のシンボルはobarrayに属しているわけで、そこらへんの関係でdefunはグローバルに定義してしまうように見えるわけだ。
そこで、obarrayをうまくやればelispでもローカル関数が直接呼べるようになるのである!!もう「elispは関数言語として中途半端」なんて呼ばせない!!
(ここで一時間くらいの時間差が)
と、思ったが、無理だった。シンボルはreadされた時点で決定してるか。
(ここで20分くらいの時間差が)
いや、いけるよ。
(defun copy-vector (v) (let* ((len (length v)) (a (make-vector len nil)) (i 0)) (while (< i len) (let* ((symv (aref v i)) (syma (intern (symbol-name symv) a))) (if (boundp symv) (set syma (symbol-value symv))) (if (fboundp symv) (fset syma (symbol-function symv)))) (setq i (1+ i))) a)) (defmacro let-fun (args &rest body) (let ((obarray (copy-vector obarray))) `(progn ,@(mapcar (lambda (a) `(fset (quote ,(intern (symbol-name (car a)))) ,@(cdr a))) args) ,@(read (prin1-to-string body))))) (let-fun ((f (lambda (x) (+ x x)))) (f (car '(3)))) ; -> 6 (f 3) ; -> void-function f
無理すぎ。再帰関数の中で使うと駄目です。
やや関連した話として、Emacs Lisp あれこれも面白いかもしれない。