- 9.1 動的なバインディングを自分なりに確認。
- ;; 追記 ここでは「環境」という概念というか言葉をつかわずにバインディングやスコープについて整理してみる、という方針。一般的には、環境をつかった方が理解ははるかに簡単。
- まず、動的ってどういうことなんだろ?
- 変数の値の決まり方の一種、かな。
- 実行時には、変数の値が存在するか、しないかのどちらかだ。
- 存在しない場合は実行エラーとなる。なので、ここでは無視する。
- すると実行時には、変数の値は必ず存在する。
- 次に外部からの入力が無いとすると、あるプログラムが実行する計算は決まっている。(逆にその計算を実行するためにつくったのがプログラムなのだが)
- すると、動的/静的といったって、それは何かプログラムの書きぶりのたぐいの話であり、できあがったプログラム(実行)という観点でみれば、決まっているものでしかないということ。
- 曖昧な表現になってしまうのだが、top-level(REPL)で定義した変数は、大域変数となる。
- ここで、大域、というのは、その変数は、同じREPLが存続している間、どこでもアクセス可能なことを指す(転じて、いつでもアクセス可能)。
CL-USER(2): (setq x 0)
0
CL-USER(3): x
0
;xという変数は、このREPLが終了するまで存在する。 - 大域変数へのアクセスは、top-level以外でも可能である。
- 大域変数へアクセスするには、
- symbol-value/setを使う。
- 大域変数しか参照の結果として得られないようなスコープ状態にする。
などの方法がある。
CL-USER(6): (let ()
(+ x 1))
1
CL-USER(7): (let ((x 100))
(+ x 1))
101
CL-USER(10): (let ((x 100))
(+ (symbol-value 'x) 1))
1 - symbol-value/setを使う。
- 大域変数というのは局所変数の自然な延長ではない。というのは、局所変数どうしのスコープも階層的になることがあり、
CL-USER(11): (let ((x 10))
(let ((x 100))
(+ x 1)))
101
これは大域変数と局所変数における、
CL-USER(13): (setq x 10)
10
CL-USER(14): (let ((x 100))
(+ x 1))
101
というものと同じような関係性ではないか、と思えるが、差異が存在する、ということ。
(おそらく、これが同じ関係性になっているのがMLのREPLなはず。) - 違う点は何か?
- それは、まず第一に、
CL-USER(11): (let ((x 10))
(let ((x 100))
(+ x 1)))
101
では、(let ((x 10)) ...で定義したxに、(let ((x 100)) ...)の...の中からアクセスする方法が存在しないが、
CL-USER(13): (setq x 10)
10
CL-USER(14): (let ((x 100))
(+ x 1))
101
において、(let ((x 100)) ...)の中で、その「外側」の(setq x 10)にアクセスする方法があるということ。
CL-USER(15): (let ((x 100))
(+ (symbol-value 'x) 1))
11 - これを基礎として、setqの振舞をみる。setqは大域変数を特別視しない。
CL-USER(57): (setq x 1)
1
CL-USER(58): (setq x (1+ x))
2
CL-USER(59): x
2
CL-USER(60): (setq x 1)
1
CL-USER(61): (let ((x 10))
(setq x (1+ x)))
11
CL-USER(62): x
1
CL-USER(63): (setq x 1)
1
CL-USER(64): (let ((x 10))
(let ((x 100))
(setq x (1+ x)))
x)
10
CL-USER(65): x
1 - setは特別視する。それによって指されるのは、局所変数を突きぬけて、大域変数である。
CL-USER(66): (set 'x 1)
1
CL-USER(67): x
1
CL-USER(68): (set 'x (1+ x))
2
CL-USER(69): x
2
CL-USER(70): (set 'x 1)
1
CL-USER(71): (let ((x 10))
(set 'x (1+ x)))
11
CL-USER(72): x
11
CL-USER(73): (set 'x 1)
1
CL-USER(74): (let ((x 10))
(let ((x 100))
(set 'x (1+ x)))
x)
10
CL-USER(75): x
101
おまけ。
CL-USER(78): (boundp 'y)
NIL
CL-USER(79): (let ((y 2))
(set 'y y))
2
CL-USER(80): (boundp 'y)
T - これらを基礎として関数はどうなるか。
- まず、top-levelにてdefunで作る関数はクロージャをつくらない。
CL-USER(23): (setq x 1)
1
CL-USER(24): (defun f ()
(+ x 1))
F
CL-USER(25): x
1
CL-USER(26): (f)
2
CL-USER(27): (setq x 100)
100
CL-USER(28): x
100
CL-USER(29): (f)
101
CL-USER(30): x
100
※これ、先のことと同じだが、MLではレキシカルなので(setq x 1)がずっと効いたはず。 - lambdaも同じ。top-levelではクロージャをつくらない。
CL-USER(35): (setq x 1)
1
CL-USER(36): (setq g #'(lambda () (+ x 1)))
#
CL-USER(37): x
1
CL-USER(38): (funcall g)
2
CL-USER(39): x
1
CL-USER(40): (setq x 100)
100
CL-USER(41): (funcall g)
101
CL-USER(42): x
100 - 比較として、クロージャをなすのはどういうときか? 局所変数どうしの重なりにおいてはクロージャをなす。
CL-USER(45): (let* ((x 1)
(h #'(lambda () (+ x 1))))
(let ((x 100))
(funcall h)))
2 - ここまでのことを整理しよう。大域変数だ局所変数だ、というのは、プログラムの実行の流れにおいて、値の参照のルールがどうなっているかという話題である。
- それはプログラマの関心からすると次のように整理できる。
- まずは、ジャンプしない流れでの関心。
- ある記号の値を参照するとき、それが何か。例: (+ x 1)のxは何?
- ある記号の値を設定するとき、どの記号を設定するか。例。(let ((x 1)) (let ((x 2)) (setq x (+ x 1)))の(setq x のxは何か。
- ある記号の値を参照するとき、それが何か。例: (+ x 1)のxは何?
- 次にジャンプする流れでの関心。
関数によって実行の流れがジャンプする。そのとき、ジャンプした先のコード(関数定義の中のコード)が、ジャンブする前との兼ね合いがありつつ、前項の話題がどうなるのか、ということ。- ある記号の値を参照するとき、それが何か。例: 関数定義の中の、(+ x 1)のxは何?
- ある記号の値を設定するとき、どの記号を設定するか。例。関数定義の中の、(let ((x 1)) (let ((x 2)) (setq x (+ x 1)))の(setq x のxは何か。
- ある記号の値を参照するとき、それが何か。例: 関数定義の中の、(+ x 1)のxは何?
- では関数でジャンプした先での「値の設定」がどうなっているかということを調べてみよう。
- まず、setは大域LOVEなので、とにかく大域を指す。
CL-USER(81): (setq x 1)
1
CL-USER(82): (defun f ()
(set 'x 2))
F
CL-USER(83): x
1
CL-USER(84): (f)
2
CL-USER(85): x
2
CL-USER(86): (setq x 1)
1
CL-USER(87): (let ((x 10))
(f))
2
CL-USER(90): x
2 - そして、setqもdefunの中では大域を指す。
CL-USER(91): (setq x 1)
1
CL-USER(92): (defun f ()
(setq x 2))
F
CL-USER(93): x
1
CL-USER(94): (f)
2
CL-USER(95): x
2
CL-USER(96): (setq x 1)
1
CL-USER(97): x
1
CL-USER(98): (let ((x 10))
(f)
x)
10
CL-USER(99): x
2 - 局所変数のかさなりでは、クロージャをなし、setqは定義時の変数を指す。
CL-USER(103): (let* ((x 10)
(g #'(lambda () (setq x 2))))
(print x)
(let ((x 100))
(print x)
(funcall g)
(print x))
(print x))
10
100
100
2
2
CL-USER(104): - これから、やはりsetqは大域変数を特別視しないことがわかる。
- また、大域(top-level)にしても、局所(let内)にしても、「同じ階層にいるとき」、setqについては、あまりレキシカルという感じはしない。つまりレキシカルというのは、スコープが階層構造をなす状況において、どの階層での実行が何を指すかについて言っているものだ、ということ。
CL-USER(158): (setq x 1)
1
CL-USER(159): (setq g #'(lambda () (setq x (1+ x))))
#
CL-USER(160): (setq x 10)
10
CL-USER(161): (funcall g)
11
CL-USER(162): (let ((x) (g))
(setq x 5)
(setq g #'(lambda () (setq x (1- x))))
(setq x 50)
(funcall g))
49
CL-USER(163): (funcall g)
12 - さて、top-levelを根っことしてletなどを使って局所変数にて階層をつくってきたのが、ここまでの作業。letなどを使うことによって、大域-局所-局所-...という階層ができたのだ(これを造語だがlet階層と呼ぶ)。そこでは、「大域-局所」なるかさなりの関係と、「局所-局所」なるかさなりの関係の調査がメインであった。
- ここで、progvを導入する。progvは、letなどとは別のかたちで、階層を成すことができる。(これを造語だが、progv階層と呼ぶ)
CL-USER(164): (setq x 0)
0
CL-USER(165): (defun f () (setq x (1+ x)))
F
CL-USER(166): (f)
1
CL-USER(167): (progv '(x) '(100) (f) x)
101
CL-USER(168): x
1
CL-USER(169): (setq x 0)
0
CL-USER(170): (defun f () (setq x (1+ x)))
F
CL-USER(171): (f)
1
CL-USER(172): (progv '(x) '(100)
(progv '(x) '(1000)
(f) x))
1001
CL-USER(173): x
1
CL-USER(175): (progv '(x) '(100)
(progv '(x) '(1000)
(f) x)
(f) x)
101
CL-USER(176): x
1 - CLの仕様としては、let階層を基本として、その根であるtop-level(大域)について、直交としてprogv階層があるという感じか。
- progv階層の影響をうけるのは、まず、set/symbol-value。
- これはprogv階層に準拠してスコープされる。
- 次に、top-levelでの関数定義内のsetq。
- これはlet階層では、その階層で呼出したとしても、常に定義時たる大域の記号を指していたが、progv階層では、それが呼び出された階層の記号を指す。(★これが動的ということ★)
- let階層を基本として直交というのは、let階層とprogv階層が混在しているとき、set/symbol-valueなどの大域にアクセスするものでなければ、記号参照はletのルールに従うということ。
- これらを確認する例。
CL-USER(179): (setq x 'top-setqed)
TOP-SETQED
CL-USER(180): (defun f () (setq x 'fun-setqed))
F
CL-USER(181): (let ((x 'letted))
(progv '(x) '('progved)
(list x (symbol-value 'x) (f))))
(LETTED 'PROGVED FUN-SETQED)
CL-USER(182): x
TOP-SETQED - これらの状況をひとことであらわすと、
「局所変数は静的(staticまたはlexical)なバインディングであり、大域変数は動的(dynamic)なバインディングである」
ということ。 - エクステントは、「いつ(まで)」その変数を利用できるかということ。
- 静的バインディングは(クロージャをつくるので)不定である。動的バインディングは(クロージャをつくらないので)フォームの実行時のみである。
CL-USER(183): (setq hoge #'(lambda ()
(let ((x 100))
#'(lambda () (setq x (1+ x))))))
#<Interpreted Function (unnamed) @ #x1000bdf1f2>
CL-USER(184): (funcall hoge)
#<Interpreted Closure (unnamed) @ #x1000bfcf92>
CL-USER(185): (setq hoge1 (funcall hoge))
#<Interpreted Closure (unnamed) @ #x1000c34fa2>
CL-USER(186): (funcall hoge1)
101
CL-USER(189): (funcall hoge1)
102
CL-USER(190): (setq hoge2 (funcall hoge))
#<Interpreted Closure (unnamed) @ #x1000cb92f2>
CL-USER(191): (funcall hoge2)
101
CL-USER(192): (funcall hoge1)
103
CL-USER(193): (setq piyo #'(lambda ()
(progv '(x) '(100)
#'(lambda () (setq x (1+ x))))))
#<Interpreted Function (unnamed) @ #x1000d98392>
CL-USER(194): (setq x 0)
0
CL-USER(195): (setq piyo1 (funcall piyo))
#<Interpreted Function (unnamed) @ #x1000e01c42>
CL-USER(196): (funcall piyo1)
1
CL-USER(197): (funcall piyo1)
2
CL-USER(198): (setq piyo2 (funcall piyo))
#<Interpreted Function (unnamed) @ #x1000e3f892>
CL-USER(199): (funcall piyo2)
3
CL-USER(200): (funcall piyo1)
4 - さて、スペシャル変数。
- proclaim: ...を公表する, 宣言する, 公布する。
- let階層のお話。
- proclaimした変数(記号)は、大域(top-level)所属になり、かつ、動的バインディングになる。
- ちょっとずつ確認。
- letはprogvがごとし、かな。ま、このかぎりにおいて、letもprogvも振舞いはそもそもかわらんのだが。
CL-USER(201): (setq x 0)
0
CL-USER(202): (let ((x 1)))
NIL
CL-USER(203): x
0
CL-USER(207): (proclaim '(special x))
T
CL-USER(208): (let ((x 1)))
NIL
CL-USER(209): x
0
CL-USER(210): - setqはsetがごとし、かな。
CL-USER(210): (let ((x 1))
(setq x x))
1
CL-USER(211): x
0 - う、違った。setqはあくまで自分がいる階層をみているし、大域変数としてのprogv的スコープが発生している。
- そうするとやはり★のところが動的たるところで、これがlet階層でも発生する、ということか。
CL-USER(211): x
0
CL-USER(212): (defun f ()
(setq x (1+ x)))
F
CL-USER(213): (f)
1
CL-USER(214): x
1
CL-USER(215): (let ((x 10))
(f)
x)
11
CL-USER(216): x
1
CL-USER(217): (setq y 0)
0
CL-USER(218): (defun g ()
(setq y (1+ y)))
G
CL-USER(219): (let ((y 10))
(g)
y)
10
CL-USER(220): y
1 - やはり、そうだ。
- ではクロージャはどうなる。
CL-USER(2): (setq hoge #'(lambda ()
(let ((x 100))
#'(lambda () (setq x (1+ x))))))
#<Interpreted Function (unnamed) @ #x1000ae19a2>
CL-USER(3): (setq hoge1 (funcall hoge))
#<Interpreted Closure (unnamed) @ #x1000b336a2>
CL-USER(4): (funcall hoge1)
101
CL-USER(7): (funcall hoge1)
102
CL-USER(8): (setq hoge2 (funcall hoge))
#<Interpreted Closure (unnamed) @ #x1000ba3a22>
CL-USER(9): (funcall hoge2)
101
CL-USER(10): (proclaim '(special x))
T
CL-USER(11): (funcall hoge2)
102
CL-USER(12): (funcall hoge1)
103
CL-USER(13): (funcall hoge1)
104
CL-USER(14): (funcall hoge1)
105
CL-USER(15): (funcall hoge1)
106
CL-USER(17): (funcall hoge1)
107
CL-USER(18): (funcall hoge2)
103
CL-USER(19): (setq hoge3 (funcall hoge))
#<Interpreted Closure (unnamed) @ #x1000c66b72>
CL-USER(20): (funcall hoge3)
Error: Attempt to take the value of the unbound variable `X'.
[condition type: UNBOUND-VARIABLE]
Restart actions (select using :continue):
0: Try evaluating X again.
1: Use :X instead.
2: Set the symbol-value of X and use its value.
3: Use a value without setting X.
4: Return to Top Level (an "abort" restart).
5: Abort entirely from this (lisp) process.
[1] CL-USER(21): :reset
CL-USER(22): (setq x 0)
0
CL-USER(23): (funcall hoge3)
1
CL-USER(24): (funcall hoge3)
2
CL-USER(25): (funcall hoge1)
108
CL-USER(26): (funcall hoge2)
104
CL-USER(27): - おお、specialにした後から、クロージャをつくらなくなる!
- で、declare
- まず基本的なスコープ動作確認。
CL-USER(2): (setq x 'top)
TOP
CL-USER(3): (let ((x 'local))
(let ((x 'special))
(declare (special x))
(let ((x 'local2))
)))
NIL
CL-USER(4): x
TOP - 通常の、大域変数の関数内利用における振舞い。let階層では動的バインディングしない。
CL-USER(5): (defun f ()
(setq x (cons 'called x)))
F
CL-USER(6): (f)
(CALLED . TOP)
CL-USER(7): x
(CALLED . TOP)
CL-USER(8): (setq x 'top)
TOP
CL-USER(9): x
TOP
CL-USER(10): (let ((x 'local))
(f))
(CALLED . TOP)
CL-USER(11): x
(CALLED . TOP) - declare specialによって、let階層でも動的バインディング。
CL-USER(12): (let ((x 'special))
(declare (special x))
(f))
(CALLED . SPECIAL)
CL-USER(13): x
(CALLED . TOP) - おまけ。proclaim specialによるlet階層での動的バインディング。
CL-USER(16): (setq x 'top)
TOP
CL-USER(19): (proclaim '(special x))
T
CL-USER(20): x
TOP
CL-USER(21): (let ((x 'let))
(f)
x)
(CALLED . LET)
CL-USER(22): x
TOP - aclのdefvarとdefconstant。
CL-USER(25): (macroexpand '(defvar *x*))
(PROGN (DECLAIM (SPECIAL *X*)) (RECORD-SOURCE-FILE '*X* :TYPE :SPECIAL-DECLARATION) '*X*)
T
CL-USER(26): (macroexpand '(defconstant one 1))
(PROGN (DECLAIM (SPECIAL ONE)) (EVAL-WHEN (COMPILE) (EXCL::DEFCONSTANT1 'ONE 1)) (RECORD-SOURCE-FILE 'ONE :TYPE :VARIABLE) (EXCL::DEFCONSTANT2 'ONE 1))
T
うーん、少々掘ってしまった。しかし、宣言に関するモヤモヤがすこし整理できた。
次回は「さまざまなデータ」。
0 件のコメント:
コメントを投稿