47

Peter Seibel の「Practical Common Lisp」という本を読んでいます。

第6章の「変数」セクションの「レキシカル変数とクロージャー」と「動的変数、別名特殊変数」。 http://www.gigamonkeys.com/book/variables.html

私の問題は、両方のセクションの例が (let ...) がどのようにグローバル変数をシャドウできるかを示しており、実際には動的変数とレキシカル変数の違いを教えていないことです。

クロージャーがどのように機能するかは理解していますが、この例の let の何が特別なのかはわかりません。

(defvar *x* 10)

(defun foo ()
  (format t "Before assignment~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "After assignment~18tX: ~d~%" *x*))


(defun bar ()
  (foo)
  (let ((*x* 20)) (foo))
  (foo))


CL-USER> (foo)
Before assignment X: 10
After assignment  X: 11
NIL


CL-USER> (bar)
Before assignment X: 11
After assignment  X: 12
Before assignment X: 20
After assignment  X: 21
Before assignment X: 12
After assignment  X: 13
NIL

ここでは特に何も起こっていないように感じます。barの外側のfooはグローバルxをインクリメントし、 letbarで囲まれたfooはシャドウされたxをインクリメントします。大したことは何ですか?これが字句変数と動的変数の違いをどのように説明するのかわかりません。しかし、この本は次のように続けています。

では、これはどのように機能するのでしょうか。LET は、 xをバインドするときに、通常の字句バインディングではなく動的バインディングを作成することになっていることをどのように認識しますか? 名前が特別に宣言されているため、それは認識されます.12 DEFVAR および DEFPARAMETER で定義されたすべての変数の名前は、自動的にグローバルに特別に宣言されます。

「通常の字句結合」を使用してletがxを結合するとどうなるでしょうか? 全体として、動的バインディングと字句バインディングの違いは何ですか? また、動的バインディングに関してこの例はどのように特別なのでしょうか?

4

5 に答える 5

59

どうしたの?

あなたは言う:ここでは特別なことは何も起こっていないように感じる。外側のfooinbarはグローバルを増分し、inxfoo囲まれているのは影付きのを増分します。大したことは何ですか?letbarx

ここで起こっている特別なことは、の値を隠すことLET ができる*x*ということです。不可能な字句変数を使用します。

コードは、を介して特別であると宣言*x*します。DEFVAR

FOO現在、の値は動的*x*に検索されます。FOOの現在の動的バインディングを取得します。*x*存在しない場合は、シンボルのシンボル値を取得します*x*。たとえば、新しい動的バインディングLETをで導入できます。

一方、字句変数は、字句環境のどこかに存在する必要があります。LET、、およびその他はLAMBDADEFUNそのような字句変数を導入できます。xここでは、3つの異なる方法で導入された字句変数を参照してください。

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

コードが次の場合:

(defvar x 0)

(let ((x 3))
  (* (sin x) (cos x)))

(lambda (x)
  (* (sin x) (cos x)))

(defun baz (x)
  (* (sin x) (cos x)))

次に、すべてのレベルでグローバルにX特別DEFVARあると宣言する宣言のために、上記の3つのケースすべてX特別でした。このため、特別な変数をとして宣言する規則があります*X*。したがって、周囲に星が付いている変数のみが特別です-慣例により。これは便利な規則です。

あなたのコードでは、次のようになります。

(defun bar ()
  (foo)
  (let ((*x* 20))
    (foo))
  (foo))

コード内で上記を介して特別*x*に宣言されているため、コンストラクトはの新しい動的バインディングを導入します。その後、が呼び出されます。内部では動的バインディングを使用しているため、現在のバインディングを検索し、動的バインディングがにバインドされていることを確認します。DEFVARLET*x*FOOFOO*x**x*20

特別な変数の値は、現在の動的バインディングにあります。

ローカルSPECIAL宣言

specialローカル宣言もあります:

(defun foo-s ()
  (declare (special *x*))
  (+ *x* 1))

変数がまたはによって特別に宣言されている場合は、ローカル宣言を省略できます。DEFVARDEFPARAMETERspecial

字句変数は、変数バインディングを直接参照します。

(defun foo-l (x)
  (+ x 1))

実際に見てみましょう:

(let ((f (let ((x 10))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (print (funcall f))))

ここでは、すべての変数が字句です。フォーム2では、関数LETのをシャドウイングしません。できません。この関数は、によって導入された字句束縛変数を使用します。フォーム2で字句的にバインドされた別の呼び出しで囲んでも、関数に影響はありません。XfLET ((X 10)X

特別な変数を試してみましょう:

(let ((f (let ((x 10))
           (declare (special x))
           (lambda ()
             (setq x (+ x 1))))))
  (print (funcall f))    ; form 1
  (let ((x 20))          ; form 2
    (declare (special x))
    (print (funcall f))))

今何?それは動作しますか?

そうではありません!

最初のフォームは関数を呼び出し、の動的な値を検索しようとしますが、X何もありません。フォーム1でエラーが発生します:X動的バインディングが有効になっていないため、バインドされていません。

LETwithspecial宣言は。の動的バインディングを導入するため、フォーム2は機能しXます。

于 2010-03-05T10:40:07.157 に答える
24

変数が字句スコープである場合、システムは関数が定義されている場所を調べて、自由変数の値を見つけます。変数のスコープが動的に設定されている場合、システムは関数が呼び出された場所を調べて、自由変数の値を見つけます。Common Lispの変数は、デフォルトではすべて字句です。ただし、動的スコープの変数は、 defvarまたはdefparameterを使用してトップレベルで定義できます。

より簡単な例

字句スコープ(setqを使用):

(setq x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 3

動的スコープ(defvarを使用):

(defvar x 3)

(defun foo () x)

(let ((x 4)) (foo)) ; returns 4

変数が字句であるか動的であるかをどのように知らせますか?そうではありません。一方、fooがXの値を検索しようとすると、最初に最上位で定義された字句値が検索されます。次に、変数が動的であると想定されているかどうかを確認します。そうである場合、fooは呼び出し元の環境を調べます。この場合は、letを使用してXの値を4にオーバーシャ​​ドウします。

(注:これは過度に単純化されていますが、異なるスコープルール間の違いを視覚化するのに役立ちます)

于 2009-01-21T01:32:35.750 に答える
10

たぶん、この例が役に立ちます。

;; the lexical version

(let ((x 10)) 
  (defun lex-foo ()
    (format t "Before assignment~18tX: ~d~%" x)
    (setf x (+ 1 x))
    (format t "After assignment~18tX: ~d~%" x)))

(defun lex-bar ()
  (lex-foo)
  (let ((x 20)) ;; does not do anything
    (lex-foo))
  (lex-foo))

;; CL-USER> (lex-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 11
;; After assignment  X: 12
;; Before assignment X: 12
;; After assignment  X: 13

;; the dynamic version

(defvar *x* 10)
(defun dyn-foo ()
  (format t "Before assignment~18tX: ~d~%" *x*)
  (setf *x* (+ 1 *x*))
  (format t "After assignment~18tX: ~d~%" *x*))

(defun dyn-bar()
  (dyn-foo)
  (let ((*x* 20))
    (dyn-foo))
  (dyn-foo))

;; CL-USER> (dyn-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 20
;; After assignment  X: 21
;; Before assignment X: 11
;; After assignment  X: 12

;; the special version

(defun special-foo ()
  (declare (special *y*))
  (format t "Before assignment~18tX: ~d~%" *y*)
  (setf *y* (+ 1 *y*))
  (format t "After assignment~18tX: ~d~%" *y*))

(defun special-bar ()
  (let ((*y* 10))
    (declare (special *y*))
    (special-foo)
    (let ((*y* 20))
      (declare (special *y*))
      (special-foo))
    (special-foo)))

;; CL-USER> (special-bar)
;; Before assignment X: 10
;; After assignment  X: 11
;; Before assignment X: 20
;; After assignment  X: 21
;; Before assignment X: 11
;; After assignment  X: 12
于 2009-02-12T21:24:17.830 に答える
9

Lispにローカル変数を動的にバインドするように指示することもできます。

(let ((dyn 5))
  (declare (special dyn))
  ... ;; DYN has dynamic scope for the duration of the body
  )
于 2009-02-13T12:43:46.437 に答える
7

PCL から例を書き直します。

;;; Common Lisp is lexically scoped by default.

λ (setq x 10)
=> 10

λ (defun foo ()
    (setf x (1+ x)))
=> FOO

λ (foo)
=> 11

λ (let ((x 20))
    (foo))
=> 12

λ (proclaim '(special x))
=> NIL

λ (let ((x 20))
    (foo))
=> 21

On Lisp、章 2.5 Scopeからのさらに別の優れた説明:

Common Lisp は、レキシカル スコープの Lisp です。Scheme は字句スコープを持つ最も古い方言です。Scheme 以前は、動的スコープは Lisp を定義する機能の 1 つと見なされていました。

レキシカル スコープとダイナミック スコープの違いは、実装が自由変数を処理する方法に帰着します。シンボルは、パラメーターとして現れるか、let や do などの変数バインディング演算子によって変数として確立されている場合、式にバインドされます。束縛されていないシンボルは自由であると言われます。この例では、スコープが機能します。

(let ((y 7)) 
  (defun scope-test (x)
  (list x y)))

defun 式内で、x は束縛され、y は自由です。自由変数が興味深いのは、それらの値がどうあるべきかが明らかでないからです。バインドされた変数の値について不確実性はありません — scope-test が呼び出されるとき、 x の値は引数として渡されたものでなければなりません。しかし、y の値はどうあるべきでしょうか? これは、方言のスコープ ルールによって回答される質問です。

動的にスコープされた Lisp では、scope-test を実行するときに自由変数の値を見つけるために、それを呼び出した関数のチェーンを振り返ります。y がバインドされた環境を見つけると、その y のバインドが scope-test で使用されるものになります。何も見つからない場合は、y のグローバル値を取得します。したがって、動的スコープの Lisp では、y は呼び出し式で持っていた値を持ちます。

> (let ((y 5)) (scope-test 3))
    (3 5)

動的スコープでは、scope-test が定義されたときに y が 7 にバインドされていても意味がありません。重要なのは、scope-test が呼び出されたときに y の値が 5 だったことだけです。

レキシカル スコープの Lisp では、呼び出し関数のチェーンを振り返る代わりに、関数が定義された時点で含まれている環境を振り返ります。レキシカル スコープの Lisp では、この例では、scope-test が定義されている y のバインディングをキャッチします。したがって、Common Lisp では次のようになります。

> (let ((y 5)) (scope-test 3))
    (3 7)

ここで、呼び出し時に y を 5 にバインドしても、戻り値には影響しません。

変数を特殊変数として宣言することで動的スコープを取得することはできますが、Common Lisp ではレキシカル スコープがデフォルトです。全体として、Lisp コミュニティは動的スコープの廃止を少しも後悔していないようです。1 つには、これは恐ろしくとらえどころのないバグにつながるものでした。しかし、字句スコープはバグを回避する方法以上のものです。次のセクションで示すように、新しいプログラミング手法も可能になります。

于 2012-09-22T06:30:34.897 に答える