Next: 5 スコープ,バインディング,エクステント
Up: 4 関数閉包(closure)
Previous: 4.2 リードマクロ
関数を返す関数の例として,たとえば,ある初期値をもらいその初期値に対し
て掛ける値をもらい,その結果を初期値の新しい値としてその変数に記憶して
おくという関数を作る関数testを以下のように定義する.
(defun test (x) #'(lambda (y) (setq x (* y x)) x))
;;(defun test (x)
;; (function (lambda (y) (setq x (* y x)) x)))
;; と同じ.
この関数testを引数を与えて呼ぶと
新たな関数が定義されることになる.
たとえば,引数として1と2を与えたものを
以下のように作れば,
> (setq x1 (test 1))
#<CLOSURE :LAMBDA (Y) (SETQ X (* Y X)) X>
> (setq x2 (test 2))
#<CLOSURE :LAMBDA (Y) (SETQ X (* Y X)) X>
変数x1, x2が表すものは別の関数クロージャとなる.これらの関数クロージャ
はfuncallにより呼び出すことが可能である.以下のようにx1に対して同じ引
数5を与えても計算結果は5, 25と変わってきている.それは,関数testの引数
xに計算結果が保存されるという形の関数定義になっているからである.
> (funcall x1 5)
5
> (funcall x1 5)
25
> (funcall x2 5)
10
> (funcall x2 5)
50
> (funcall x1 5)
125
> (funcall x2 5)
250
このように,クロージャを返す関数の場合,関数への引数はクロージャの中に
保存される.つまり,その引数はクロージャの評価環境として残り続けると
いうわけである.では,どの範囲のものが残り続けるかといえば,そのクロージャ
が定義された関数時の局所変数である.
たとえば,局所変数を作るletを用いて,
(defun let-closure (z)
(let ((x 1))
#'(lambda (y) (setq x (* x y) z (* z y)) (list x y z))))
という関数let-closureを定義すると,(let-clousre 1)や(let-closure 2)な
どのように呼び出すことでそれぞれ異なるクロージャが定義される.
> (setq l1 (let-closure 1))
#<CLOSURE :LAMBDA (Y)
(SETQ X (* X Y) Z (* Z Y)) (LIST X Y Z)>
> (setq l2 (let-closure 2))
#<CLOSURE :LAMBDA (Y)
(SETQ X (* X Y) Z (* Z Y)) (LIST X Y Z)>
;;; 古いFranz CL(Allegro CL 3.0.1.beta)の場合.
> (setq l1 (let-closure 1))
(EXCL::.LEXICAL-CLOSURE.
(LAMBDA (Y) (SETQ X (* X Y) Z (* Z Y))
(LIST X Y Z))
((X . 1) (Z . 1)) NIL
((LET-CLOSURE . EXCL::INVALID)) NIL)
> (setq l2 (let-closure 2))
(EXCL::.LEXICAL-CLOSURE.
(LAMBDA (Y) (SETQ X (* X Y) Z (* Z Y))
(LIST X Y Z))
((X . 1) (Z . 2)) NIL
((LET-CLOSURE . EXCL::INVALID)) NIL)
この関数クロージャの生成時においては,変数x,z が局所変数であり,
これら二つの変数の値が保存された形でクロージャが生成されている.
l1,l2をそれぞれfuncallにより実行すると以下のようにx,y,zの値を
返す.
> (funcall l1 5)
(5 5 5)
> (funcall l1 5)
(25 5 25)
> (funcall l2 5)
(5 5 10)
> (funcall l2 5)
(25 5 50)
> (funcall l1 5)
(125 5 125)
> (funcall l2 5)
(125 5 250)
generated through LaTeX2HTML. M.Inaba 平成18年5月7日