Common Lisp의 동적 바인딩

Oct 19 2020

이 질문은 Common Lisp 범위 지정 (동적 대 어휘) 의 확장입니다.

Common Lisp에서 범위 및 범위의 개념을 읽고 이해했습니다 (링크 : https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html), 그러나 다음 세 가지 예에 대해 머리를 이해할 수 없습니다. 모든 예제는 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 : x는 defvar 에 의해 100으로 설정 되었지만 fun1 이 호출 될 때 왜 5로 재설정되지 않습니까? 함수가 호출 될 때 바인딩이 발생한다고 생각 했습니까? 아니면 함수가 정의되었을 때입니까? fun1이 정의되었을 때 x는 아직 바인딩되지 않은 것으로 보입니다. 따라서 fun1 (어휘 범위가 지정됨)의 x 바인딩은 프로그램의 나머지 부분에서 볼 수 없으며 "글로벌"바인딩은 후속 defvar 와 함께 발생합니다 . 함수 호출의 동작 x는 fun1의 어휘 섀도 잉으로 인한 것이지만 fun2 에는 동적 섀도 잉이 없습니까 ? 즉 fun1이 x를 먼저 정의하고 당시 "글로벌"x를 보지 않았기 때문에 여기에 x의 두 가지 인스턴스가 있습니다.
  • 예제 3 : x가 전역 적으로 먼저 설정 되었기 때문에 fun1과 fun2가 동일한 x 인스턴스를 참조하고 있으므로 fun1 중에 값이 업데이트되고 fun2 중에도 적용됩니다 (둘 다 5)? 또한 마지막에 x 값을 요청하면 100을 얻습니다 (왜? fun2가 5를 반환 할 때?)

Guy Steel의 Common Lisp 책에서 발췌 한 다음 발췌 내용과 관련이 있지만 머리를 감쌀 수는 없습니다.

"어휘 범위를 사용하는 구성은 각 실행에서 설정된 각 엔티티에 대해 새 이름을 효과적으로 생성합니다. 따라서 동적 섀도 잉이 발생할 수 없습니다 (어휘 섀도 잉이 발생할 수 있음). 이는 동적 범위가 관련 될 때 특히 중요합니다."

다음 문장이 항상 참입니까 (출처 : https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf) :

Lisp의 바인딩 규칙은 다음과 같습니다. 이름의 사용은 여전히 ​​활성 상태 인 해당 이름의 가장 최근 선언에 바인딩됩니다.

나는 일부 부분을 이해하기 시작했지만 세 부분을 모두 완전히 이해할 수는 없으므로 도움이 될 수 있다면 매우 도움이 될 것입니다.

답변

4 coredump Oct 19 2020 at 04:18

예제 1 : 범위가 무한한 변수 x는 fun1에서 5로 설정되어 있으므로 fun2는이 값에 액세스합니다. 이것이 올바른 해석입니까?

대부분, 그것에 대해 확장하겠습니다.

x에 의해 선언 된 defvar변수는 특수로 선언되고 있으며, 지금부터에 x되어 항상 특별한 변수로 간주, 동적 바인딩. 전화 할 때 :

(fun1 5)

의 바인딩 fun1은 동적으로 수행됩니다. 즉,의 반환 값 fun1fun2의 현재 동적 바인딩을 기반으로합니다 x.

예 2 : [...] 즉 fun1이 x를 먼저 정의하고 당시 "글로벌"x를 보지 않았기 때문에 여기에 x의 두 개의 다른 인스턴스가 있습니다.

예, 그러나 이것은 모든 통역사에게 해당되는 것은 아닙니다 (Sylwester의 답변 참조). 당신이 정의 할 때 fun1, x특별한 것으로 알려져 있지 않다; 이는이 시점에서 매개 변수의 범위 x가 어휘 임을 의미합니다 . 나중에 defvar가 평가 될 때 xin 의 바인딩 fun1은 여전히 ​​어휘이므로 이러한 호출 fun1은 전역 변수의 동적 바인딩을 수정하지 않습니다 x.

예 3 : [...] 또한 끝에 x의 값을 요청하면 100을 얻습니다 (왜? fun2가 5를 반환 할 때?

특수 변수는 무한한 범위를 가지며 모든 곳에서 볼 수 있지만 바인딩에는 동적 범위 가 있습니다. 즉, 바인딩은이를 설정하는 형식만큼만 지속됩니다.

여기 x에서 최상위에서 요청할 때 전역 적으로 x, 100에 바인딩 된 값을 갖게됩니다 . 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에서는 두 개의 첫 번째 예제에서 똑같이 동작하며, 이동하면서 함수를 컴파일하면 출력과 정확히 동일하게 작동합니다.

동적 범위는 어휘 범위가 적용되지 않음을 의미합니다.

(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에 인쇄하고 있다고 생각하지만 실행 중에 출력 스트림을 래핑하고 변경했습니다.