next up previous
Next: 5 スコープ,バインディング,エクステント Up: ソフトウェア特論 講義資料 クロージャ,オブジェクト指向 Previous: 3 Funarg問題(Function Argument Problem)


4 関数閉包(closure)

関数をデータとして扱った場合のfunarg問題は,関数を定義した時 点の環境とその関数が実行される場合の環境との二つの環境の区別を明確に してない場合におこります.関数に環境を付加したものをクロージャとして とりあつかうことで,この問題を解決することができます. クロージャは関数をデータとして扱うために考えられたものです.関数をデー タとして扱う場合には,関数を定義した時点の環境とその関数が実行される場 合の環境との二つの環境をどのように区別するかということが問題となります. そこで,CommonLispでは,関数データが定義された時点の局所変数の環境デー タをすべてその関数データの中に保存した形の関数データを考え,取り扱える ようになっています.そのように環境も保存した関数データであるためにクロー ジャと呼ばれます. CommonLispではクロージャが定義された時の局所変数のみ(変数以外では,局 所関数,タグ,局所マクロ,ブロックなども保存される)を保存するために, レキシカルクロージャ(lexical closure)と呼ばれます. functionというスペシャルフォームに与えられた関数シンボルあるいはラムダ 式は,レキシカルクロージャを返します.クロージャは,そのクロージャが生 成される際の変数のバインディング情報が閉じ込められた関数定義です.そし て,レキシカルクロージャとは,スコープがレキシカルのルールにのっとって いるクロージャです.クロージャはいわば環境を伴った関数データといえます. functionは通常#'というリードマクロで使われていますが,これを用いた関 数を返す関数はクロージャと呼ばれるデータ構造を作ります.たとえば,ある 初期値をもらいその初期値に対して掛ける値をもらい,その結果を初期値の新 しい値としてその変数に記憶しておくという関数を作る関数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月6日