CommonLispでの動的バインディング

Oct 19 2020

この質問は、Common Lispスコープの拡張です(動的vs字句)

Common Lispのスコープとエクステントの概念を読んで(うまくいけば)理解しました(リンク: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html)が、次の3つの例に頭を悩ませることはできません。すべての例は、SBCL / Slime / Emacsの新しいlispセッションで実行されます。

例1:5と5を印刷する

(defvar x 100)
(defun fun1 (x)
   (print x)
   (fun2))

(defun fun2 ()
   (print x))
     
(fun1 5)

例2:5と100を出力

 (defun fun1 (x)
   (print x)
   (fun2))

 (defun fun2 ()
   (print x))
    
 (defvar x 100)
 
 (fun1 5)

例3:5&5&100を出力

(defvar x 100)

(defun fun1 (x)
  (print x)
  (fun2))

(defun fun2 ()
  (print x))

(defvar x 100)
     
(fun1 5)

x

fun1が常に5を出力する理由を理解しています(字句スコープのためですが、間違っている場合は修正してください)。私が理解していないのは、fun2が例1で5を出力し、例2で100を出力し、例3で再び5を出力する理由です。

  • 実施例1:X、不定スコープを持つ変数は、5に設定されているFUN1、従ってfun2この値にアクセスします。これは正しい解釈ですか?
  • 例2:defvarによってxが100に設定されていますが、fun1が呼び出されたときに5に再設定されないのはなぜですか?バインディングは関数が呼び出されたときに行われたと思いましたか、それとも定義されたときに行われましたか?fun1が定義されているとき、xはまだバインドされていないようです。したがって、fun1(字句スコープ)でのxのバインドは、プログラムの残りの部分には表示されず、「グローバル」バインドは後続のdefvarで行われます。関数呼び出しの動作xは、fun1の字句シャドウイングが原因ですが、fun2の動的シャドウイングはありませんか?つまり、fun1が最初にxを定義し、その時点で「グローバル」xを認識していなかったため、ここには2つの異なるxのインスタンスがあります。
  • 例3:xが最初にグローバルに設定されているため、fun1とfun2の両方がxの同じインスタンスを参照しているため、その値はfun1中に更新され、fun2中にも適用されます(両方とも5)?さらに、最後にxの値を要求すると、100が得られます(なぜ?fun2が5を返すのですか?)

それはGuySteelのCommonLispの本からの次の抜粋と関係がありますが、私はそれについて頭を悩ませることができません:

「字句スコープを使用する構造は、実行ごとに確立されたエンティティごとに新しい名前を効果的に生成します。したがって、動的シャドウイングは発生しません(字句シャドウイングは発生する可能性があります)。これは、動的エクステントが関係する場合に特に重要です。」

次のステートメントは常に正しいですか(出典: https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):

Lispのバインディングルールはこれです:名前の使用は、まだ生きているその名前の最新の宣言にバインドされます。

いくつかの部分を理解し始めていますが、3つの部分すべてを包括的に理解することはできないので、助けていただければ非常に役立ちます。

回答

4 coredump Oct 19 2020 at 04:18

例1:スコープが不定の変数であるxは、fun1で5に設定されているため、fun2はこの値にアクセスします。これは正しい解釈ですか?

ほとんどの場合、それについて詳しく説明します。

ときxによって宣言されdefvar、変数は特別として宣言されている、と今から上xされ、常に特別な変数として見ると、動的に結合しました。電話をかけるとき:

(fun1 5)

結合には、fun1戻り値の両方を意味し、動的に行われているfun1とfun2の結合現在の動的に基づいていますx。

例2:[...]つまり、fun1が最初にxを定義し、その時点で「グローバル」xを認識していなかったため、ここには2つの異なるxのインスタンスがあります。

はい。ただし、これはすべての通訳者に当てはまるわけではありません(Sylwesterの回答を参照)。を定義するときfun1、x特別であるとは知られていない。つまり、この時点でのパラメーターのスコープxは字句です。後で、defvarが評価されるとき、xinのバインディングfun1はまだ字句であり、そのfun1ため、呼び出しはグローバル変数の動的バインディングを変更しませんx。

例3:[...]さらに、最後にxの値を要求すると、100が得られます(なぜ?fun2が5を返しているのですか?

特別な変数のスコープは不定で、どこにでも表示されますが、バインディングには動的な範囲があります。つまり、バインディングは、それを確立するフォームの間だけ存続します。

ここxで、トップレベルで要求すると、グローバルにバインドされた値がx100になります。値5はx、呼び出しfun1が有効になっている間だけ一時的にバインドされます。

SETFバインディングを変更するために使用した場合は、グローバルバインディングを変更できますが、これは関数適用またはletバインディング中に発生することではありません。

5 RainerJoswig Oct 19 2020 at 15:03

コードへのいくつかの注釈:

例1

(defvar x 100)    ; declares X to be special, globally and locally
                  ; also sets X to 100

(defun fun1 (x)   ; X is a dynamically bound variable
  (print x)       ; lookup of dynamic binding of X
  (fun2))        

(defun fun2 ()
  (print x))      ; lookup of dynamic binding of X
     
(fun1 5)

例2

 (defun fun1 (x)  ; X is a lexical local variable
   (print x)      ; lexical reference to X
   (fun2))

 (defun fun2 ()
   (print x))     ; X is undeclared/undefined
                  ; the exact behaviour is undefined in Common Lisp
                  ; many implementations assume dynamic lookup of X
                  ; most compilers will show a warning
                  ; CMUCL also by default declared X globally to be special
                  ; -> don't use this in your code
    
 (defvar x 100)   ; declares X to be special, globally and locally 
                  ; also sets X to 100     
 (fun1 5)

例3

(defvar x 100)    ; declares X to be special, globally and locally
                  ; also sets X to 100

(defun fun1 (x)   ; X is a dynamically bound variable
  (print x)       ; lookup of dynamic binding of X
  (fun2))

(defun fun2 ()
  (print x))      ; lookup of dynamic binding of X

(defvar x 100)    ; does nothing
                  ;  -> X is already declared special
                  ;  -> X already has a value
                  ;     see also: DEFPARAMETER
     
(fun1 5)

x                 ; lookup of global (or thread local) value of X
3 Sylwester Oct 19 2020 at 04:19

あなたのトリックは、実装によって動作が異なります。例えば。その場で関数をコンパイルしないCLISPでは、最初の2つの例でまったく同じように動作し、関数をコンパイルすると出力とまったく同じように動作します。

動的スコープとは、字句スコープが適用されないことを意味します。

(defparameter *test* 100)

(defun print-test ()
  (print *test*))

(defun call-print-test-with (*test*)
  (print-test))

(print-test)         ; prints 100
(call-print-test 10) ; prints 10

*test*動的(グローバル)であるため、同じ名前のローカル変数を変更すると、それを上書きするスコープがなくなるまで一時的に上書きされます。これがダイナミックの意味です。

*test*字句スコープが設定されている場合、両方が出力されます100。

これが、常に*earmuffs*グローバルで使用する必要がある理由です。あなたが持つ変数のどこかを定義した場合defvar、またはdefparameterパラメータまたはローカル変数のどこかに同じを使用して、あなたは知らなくても、一時的に変数を変更する場合があります、それが起こる場所を見つけるのは非常に難しいかもしれません!人々*earmuffs*がパラメータを見て、letこれが意図であると理解するとき。例えば。

(with-output-to-string (*standard-output*)
  (some-function-whose-printed-output-you-want))
; ==> a string with the actual output

呼び出される関数は賢明ではありません。stdoutに出力していると思われますが、実行中にラップして出力ストリームを変更しました。