プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(16)

労働はつまらないですねえ。
帰宅できたので「実践Common Lisp」第6章で変数を勉強します。

Common Lispはレキシカル変数とダイナミック変数という
2種類の変数をサポートしており、これらは大雑把に言うと
他の言語におけるローカル変数とグローバル変数に対応するそう。
まあ、やっていきます。

変数の基礎

変数…値を保持できる名前付きの場所のこと
今更言うことでもありませんが、Common Lispは変数の宣言時に
型を宣言する必要のない動的型付け言語です。
全ての型エラーを検出するという意味で強い型付けの言語であり、
あるオブジェクトを別のクラスのインスタンスとして扱う方法は
存在しないということです。

Common Lispにおける全ての値は、概念上はオブジェクトへの参照であり、
変数に新しい値を代入しても「変数がどのオブジェクトを参照するのか」が
変化するのであって、前に参照していたオブジェクトには影響しません。
ぼんやりとは理解できる気がするので先に進みます。

新しい変数を導入する方法の1つはお馴染みDEFUNです。
定義時にパラメータリストが関数呼び出し時の引数を保持する
変数を定義してくれます。
Lispは関数が呼ばれるたびに、呼び出し元から渡された引数を
保持するための新しい束縛(binding)を作るそうです。

他の新しい変数を導入する方法としては
LETという特殊オペレータがあります。

(let (variable)
  (body-form))

最初の引数は変数の初期化フォームです。
変数名とその初期値からなるリストか、変数名単体(NILが入る)が
ここにきます。
LETフォームが評価される時は初期値のフォームがまず評価され、
それぞれが対応する変数に束縛され、本体(body-form)が評価されます。

関数のパラメータやLETの変数のスコープは変数を導入したフォームに
制限されます。それらのフォームは束縛フォームと呼ばれるそうです。
同じ名前の変数を導入する束縛フォームを入れ子にすると、
最も内側の変数の束縛がソレより外の変数の束縛を隠してしまいます。
これは他の言語と同じですね。

LETの亜種としてLET*というのもいるということ。

CL-USER> (let* ((x 10)
		(y (* 2 x)))
	   (+ x y))
30
CL-USER>

LET*を使えば各変数が参照する初期値のフォームで
変数リストの中で前に出てきたものを使うことができるそうです。
LABELSの変数バージョンってところですかね。

レキシカル変数とクロージャ

束縛フォームが導入する変数は全て、
デフォルトではレキシカルなスコープを持っているらしいです。
ただしCommon Lispのレキシカル変数は多少ひねりが効いていて、
そのひねりは入れ子になった関数とレキシカルスコープの
組み合わせからくるものだそうです…。

CL-USER> (let ((count 0))
	   #'(lambda () (setf count (1+ count))))

無名関数に覆われたスコープの中からレキシカル変数を参照したら
どうなるだろう?と例に出されたのがこれです。

う〜ん、普通に参照できるだろうし、どうにもならないのでは?
と思っています。
こいつを実行すると…

CL-USER> (let ((count 0))
	   #'(lambda () (setf count (1+ count))))
#<CLOSURE (LAMBDA ()) {100350D97B}>
CL-USER>

こうなりました。一瞬戸惑いましたが、まあ、そうか。
別にラムダ式は実行されていないですもんね。
最後に関数オブジェクトを置いたから、それがそのまま評価されてこうなると。

この戻り値はFUNCALLで実行できるらしいです。うん、まだ着いていけます。
そしてcountがレキシカル変数の時はちゃんと動作するとのこと。
うん?レキシカル変数じゃないと動作しないのでしょうか。
とりあえず続けます。
LETフォームに制御が入った時に作られたcountの束縛は、
必要とされる間ずっとくっついてくるそうです。
このようになった無名関数をクロージャというそうです。
先程の返り値もクロージャって名前についてましたね。

クロージャで大事なのは、束縛が補足される点だそうです。
クロージャは閉じ込めた変数の値にアクセスできるだけでなく、
閉じ込めた変数に新しい値を代入して次にクロージャを呼び出すまで
それを保持できるということです。

むむむ。途中で振り落とされました。とりあえずやってみます。

CL-USER> (defparameter *foo*
	   (let ((count 0))
             #'(lambda () (setf count (1+ count)))))
*FOO*
CL-USER> (funcall *foo*)
1
CL-USER> (funcall *foo*)
2
CL-USER>

ああ、凄いです。うおお、わかるんだけど言語化できない。これはあんまりわかってないからか?
関数がオブジェクトであることの利点をはっきりと見た気がします。
レキシカル変数でできるというのは、束縛を関数オブジェクトの中に
補足するからでしょうか?
色々と用語の使い方が合っているかわかりません。難しいなあ。
それはともかくちょっといじってみます。

CL-USER> (defparameter *hoge*
           #'(lambda () (setf *count* (1+ *count*))))
*HOGE*
CL-USER> (funcall *hoge*)
1
CL-USER> (funcall *hoge*)
2
CL-USER> *count*
2
CL-USER> *hoge*
#<FUNCTION (LAMBDA ()) {1001E80B8B}>
CL-USER>

できるやんけ!と思ったけど当たり前でした。
ただグローバル変数に1を足しているだけの関数です。
クロージャじゃありませんでした。

1つのクロージャに複数の変数の束縛を閉じ込めることも、
複数のクロージャが同じ束縛を補足することもできるそうです。
前者はただ複数の変数を宣言しときゃいいだけな気がします。

CL-USER> (defparameter *foo*
	   (let ((x 0)
	         (y 1))
	     #'(lambda () (* (setf x (1+ x)) (setf y (* y 2))))))
*FOO*
CL-USER> (funcall *foo*)
2
CL-USER> (funcall *foo*)
8
CL-USER>

うん、できてそうです。
後者は…まあやってみます。

CL-USER> (let ((count 0))
	     (list
	      #'(lambda () (incf count))
	      #'(lambda () (decf count))
	      #'(lambda () count)))
(#<CLOSURE (LAMBDA ()) {1004248EDB}> #<CLOSURE (LAMBDA ()) {1004248EFB}>
 #<CLOSURE (LAMBDA ()) {1004248F1B}>)
CL-USER> (defparameter *foo*
	   (let ((count 0))
	     (list
	      #'(lambda () (incf count))
	      #'(lambda () (decf count))
	      #'(lambda () count))))
*FOO*
CL-USER> (mapcar #'funcall *foo*)
(1 0 0)
CL-USER> (mapcar #'funcall *foo*)
(1 0 0)
CL-USER>

結局参考書を写経しました。
関数オブジェクトのリストかあ。そうすりゃよかったのか…。
ただ、想定した動きをしていません。
間違いなくクロージャにはなっているんですけどね。

ただ、もう日をまたぎそうな時間なので続きは明日にします。
やっぱりただ参考書に書いてあることをタイピングしてるだけじゃ
ダメですね。勉強としても著作権的にも。
頭と手を動かすことをもっと意識していきたいです。

それでは、おやすみなさい。