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 あれこれも面白いかもしれない。