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

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

Common Lispを頑張る(17)

今日も「実践Common Lisp」です。
昨日の最後に動かしてみた関数、動きが想定と違うなんて書きましたが、
間違っていたのは多分想定のほうですね。
朝ヨーグルトを食べていたら突然気が付きました。
incfで増やしてdecfで減らしているので最終的なcountの値は
何回関数オブジェクトを呼び出しても変わらない感じでした。とほほ。
…まあ、今日は今日でやっていきます。ダイナミック変数からです。

ダイナミック変数

レキシカル変数は良いものですが、たまにはどこからでも参照できる
変数が欲しくなりますよね。そこでダイナミック変数です。
グローバル変数Common Lispでそう呼んでいるのかと思いましたが、
より便利で扱いやすいらしいです。どれどれ。

ダイナミック変数を使う方法は2つあり、DEFVARとDEFPARAMETERです。
もうご存知ですよね。僕も知っているので割愛します。
あ、一つだけ。DEFVARで値を指定せずに変数を定義したとき、
そのような変数は”未束縛”であるというそうです。

CL-USER> (defvar *hey*)
*HEY*
CL-USER> *hey*
; Evaluation aborted on #<UNBOUND-VARIABLE *HEY* {10042DF373}>.
CL-USER> (defvar *hey* 'hey-yo)
*HEY*
CL-USER> *hey*
HEY-YO
CL-USER>

なるほど。エラーメッセージのところで未束縛の変数を参照したから
怒られている感じがしますね。
(というか定義してない変数を呼ぶと同じエラーで怒られますね)
あとDEFPARAMETERで第二引数を取らずに変数名だけ指定して
同じようにやったら怒られたのでそこらへんも違うようです。

あと使い分けです。これはあんまり意識していなかった。
DEFVAR:その変数を使うソースコードを変更したとしても維持したいデータ。
DEFPARAMETERは…途中で書き換えるデータですかね。

グローバルな経数の利点は、引数として渡す必要がないところです。
そしてグルーバルな変数を参照するような関数を書いていると、
時々その変数を書き換えたくなるのはよくあることですよね。
でも、書き換えたら戻さなくては他の関数が思わぬ被害を受けたりします。

別にグローバルな変数に影響を及ぼしたいわけではなく、
ほんの一瞬だけ書き換えたい、用事が済んだら戻っていてほしいというのが
わがままな希望であります。
と、そんなワガママを叶えてくれるのがダイナミック変数らしいです。
ダイナミック変数をLETフォームや関数のパラメータで束縛すると、
束縛フォームが続いている間はグローバルな変数の束縛が
置き換えらるらしいです。束縛フォームのレキシカルスコープでしか
参照できないレキシカルな束縛と違って、ダイナミックな束縛は
束縛フォームを実行している間に起動されるどんなコードからでも
参照できるといいます。
ここまでほとんど参考書の丸パクリ文章です。全然噛み砕けません。
単純に、同名の変数が複数ある時は一番内側のスコープのものが
参照されるというのとは違う話なのでしょうか…。
多分違う話なんでしょうけども…実際に書かないとダメですね。

CL-USER> (defparameter *hey* "nice to meet you")
*HEY*
CL-USER> (defun foo () (format t "hey=>~a~%" *hey*))
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
CL-USER> (foo)
hey=>nice to meet you
NIL
CL-USER>

LETフォームで*hey*を束縛し別の値にした上でfooを呼びます。

CL-USER> (let ((*hey* "how are you?")) (foo))
hey=>how are you?
NIL
CL-USER> (foo)
hey=>nice to meet you
NIL
CL-USER>

LETフォームの中では新しい束縛に上書きされ、
そこから出れば元の値に戻ります。
納得がいかないわけではなく、あまりに当然の動きに見えます。
参考書の通り続けていきます。

CL-USER> (defun bar ()
	   (foo)
	   (let ((*hey* "how are you?")) (foo))
	   (foo))
BAR
CL-USER> (bar)
hey=>nice to meet you
hey=>how are you?
hey=>nice to meet you
NIL
CL-USER>

連続でfooを呼び出すbar関数です。
2回目の呼び出しのときだけ*hey*を束縛しているのでこうなります。

CL-USER> (defparameter *hey* "Nice to meet you")
*HEY*
CL-USER> (defun foo ()
	   (format t "A : ~a~%" *hey*)
	   (setf *hey* (concatenate 'string *hey* "!"))
	   (format t "B : ~a~%" *hey*))
WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
CL-USER> (foo)
A : Nice to meet you
B : Nice to meet you!
NIL
CL-USER> *hey*
"Nice to meet you!"
CL-USER>

ちょいとfooを変えました。2行出力し、2行目の出力の前に
ビックリマークを行末に追加します。
ダイナミック変数をsetfで変更しちゃっているので、
実行後は*hey*の値が変わってしまいます。
今度はこれをbarで呼びます。

CL-USER> (bar)
A : Nice to meet you!
B : Nice to meet you!!
A : how are you?
B : how are you?!
A : Nice to meet you!!
B : Nice to meet you!!!
NIL
CL-USER>

う〜む…何が特別なのかまだわかりません。
自分は何か思い違いをしているような気がします。

色々試してみるしかありませんね。
昨日のレキシカル変数を使った関数を書き換えてみます。

CL-USER> (defparameter *count* 0)
*COUNT*
CL-USER> (defparameter *foo*
	   (let ((*count* 0))
             #'(lambda () (setf *count* (1+ *count*)))))
*FOO*
CL-USER> *foo*
#<FUNCTION (LAMBDA ()) {1001CF915B}>
CL-USER>

ここまでやって昨日も同じことしてたのに気が付きました。
グローバルの値を書き換えちゃうんでした。
じゃあ、これをどうやってダイナミック変数にするかです。
labmda式じゃなくすれば良いのでしょうか。

CL-USER> (defparameter *count* 10)
*COUNT*
CL-USER> (defparameter *foo*
	   (let ((*count* 0))
             (setf *count* (1+ *count*))))
*FOO*
CL-USER> *foo*
1
CL-USER> *count*
10
CL-USER>

束縛できているようです。

これは…ダメですね、レキシカル変数もダイナミック変数も
全然理解できていないです。

悔しいですが、それ以上に眠いのでここまでにします。
明日はここ2日間のおさらいになりそうな気がします。

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