2008年9月20日土曜日

【CL入門】9 宣言 (その2)

こつこつ。

  • 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

  • 大域変数というのは局所変数の自然な延長ではない。というのは、局所変数どうしのスコープも階層的になることがあり、

    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は何?
    • ある記号の値を設定するとき、どの記号を設定するか。例。関数定義の中の、(let ((x 1)) (let ((x 2)) (setq x (+ x 1)))の(setq x の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 件のコメント: