子を生むクロージャ

LOLでlabelsを使ったクロージャが紹介されている(On Lispから借りている)。ごく簡単に端折って書くと

(defun f1 ()
  (labels
      ((foo () 'bar))
    #'foo))

(f1)
|
#<lexical-closure: (anonymous)>

このようにlabelsで定義した関数を取り出すことができる。これはlabels内で定義された他の関数も参照できるクロージャである。

(defun f2 ()
  (let (op (counter 0))
    (labels
        ((up () (if (< 4 (incf counter))
                    (setq op  #'down))
           counter)
         (down () (if (< (decf counter) 1) (setq op  #'up))
           counter)
         (call (&optional new) (if new #'call (funcall op))))
      (setq op #'up) #'call)))

(setq x (f2))
|
#<lexical-closure: (anonymous)>

(funcall x)
|
1

(funcall x)
|
2

(setq y (funcall x t))
|
#<lexical-closure: (anonymous)>

(funcall y)
|
3
;環境はxと同じ。

中の関数をスイッチすることで挙動を変更できるし、関数を取り出して別のシンボルに束縛することもできる。外に取り出しても関数の環境は定義されたときの環境のままである。


新しい環境を持ったコピーを作りたい場合、コピーを作る関数は外部で定義されなくてはならない。

(setq z (f2))
|
#<lexical-closure: (anonymous)>

(funcall z)
|
1
;新しいクロージャ

クロージャの内部でコピーを作る関数を使用するには、生成関数を定義したクロージャ内で、その生成関数にアクセスすることになる。自分のコピーを作るために、letの中身をlabelsにする必要はなかった。

(defun f3 (grand)
  (labels
      ((new (g)
         (let ((counter g))
           (lambda ()
             (if (< (decf counter) 0) (new (1+ g))
               (show counter)))))
       (show (n) (format t "<~A>" n)))
    (new grand)))

(setq x (f3 2))
|
#<lexical-closure: (anonymous)>

(setq y (funcall x))
|
<1>

(setq y (funcall x))
|
<0>

(setq y (funcall x))
|
#<lexical-closure: (anonymous)>

(funcall x)
|
#<lexical-closure: (anonymous)>

(funcall y)
|
<2>

xの中で新しいクロージャが作られている。自分と同じ構造で、別々の環境を持つクロージャが作れるので、これは子を生むクロージャである。


これと、一つ上の形でopにクロージャを束縛して呼び出すようにすると世代交代をするクロージャや、世代間を行き来するクロージャが作れる。

(defun f4 (grand)
  (let (op)
    (labels ((new (g)
               (let ((hoge `(,g)))
                 (lambda () (if (caddr hoge) (progn (setq op (new (cadr hoge)))
                                               (list (cadr hoge)))
                              (push (1+ (car hoge)) hoge)))))
             (call () (funcall op)))
      (setq op (new grand))
      #'call)))

(setq x (f4 0))
|
#<lexical-closure: (anonymous)>

;世代交代するクロージャ
(dotimes (a 10)(format t "~A~%" (funcall x)))
|
(1 0)
(2 1 0)
(1)
(2 1)
(3 2 1)
(2)
(3 2)
(4 3 2)
(3)
(4 3)

(defun f5 (m0 n0 &optional (verbose t))
  (labels
      ((new (m n)
         (let (op)
           (labels
               ((process ()
                  (lambda ()
                    (if (functionp n)
                        (progn (setq n (funcall n)) op)
                      (progn
                        (if verbose (format t "~A ~A~%" m n))
                        (cond ((zerop m) (1+ n))
                              ((zerop n) (new (1- m) 1))
                              (t (new (1- m) (new m (1- n)))))))))
                (call () (if (functionp op) (setq op (funcall op))
                           (progn (if verbose (format t "~A~%" op)) op))))
             (setq op (process)) #'call))))
    (new m0 n0)))

;世代を行き来するクロージャ
(setq x (f5 2 1))
(dotimes (a 15) (funcall x))
|
2 1
2 0
1 1
1 0
0 1
0 2
1 3
1 2
1 1
1 0
0 1
0 2
0 3
0 4
5

f4はlistが伸びると新しい環境に要素を写し、自分自身に上書きする。f5はアッカーマン関数を一つずつ展開しながら計算する関数で、展開が済むまで子関数を再帰的に呼び出す。どちらも生成関数を内部から呼び出すことで自身の後継を作るクロージャとなっている。