3

次のスキームコード

(let ((x 1))
   (define (f y) (+ x y))
   (set! x 2)
   (f 3) )

これは 4 ではなく 5 に評価されます。Scheme が静的スコープを促進していることを考えると驚くべきことです。クロージャー内のクローズド環境で後続のミューテーションがバインディングに影響を与えることを許可すると、動的スコープに戻るようです。許可されている特定の理由はありますか?

編集:

上記のコードは、私が懸念している問題を明らかにするのにあまり明白ではないことに気付きました。以下に別のコードフラグメントを配置します。

(define x 1)

(define (f y) (+ x y))

(set! x 2)

(f 3)  ; evaluates to 5 instead of 4
4

4 に答える 4

5

ここで混乱しているのは、スコーピングとメモリを介した間接参照の2つのアイデアです。x字句スコープは、への参照が常にバインディングx内ののバインディングを指すことを保証しますlet

あなたの例ではこれに違反していません。概念的には、letバインディングは実際にはメモリ内に(を含む1)新しい場所を作成しており、その場所はにバインドされた値xです。場所が逆参照されると、プログラムはそのメモリ場所で現在の値を検索します。を使用するset!と、メモリに値が設定されます。(字句スコープを介して)バインドされた場所にアクセスできるパーティのみがx、メモリ内のコンテンツにアクセスまたは変更できます。

f対照的に、動的スコープでは、にバインドされた場所へのアクセスを許可したかどうかに関係なく、任意のコードで参照している値を変更できますx。例えば、

(define f
  (let ([x 1])
    (define (f y) (+ x y))
    (set! x 2)
    f))

(let ([x 3]) (f 3))

6動的スコープを持つ架空のスキームで返されます。

于 2012-11-14T04:09:10.660 に答える
4

そのような突然変異を許容することは素晴らしいことです。これにより、事前に準備された手段を介してのみアクセスできる内部状態を持つオブジェクトを定義できます。

(define (adder n)
  (let ((x n))    
    (lambda (y)
      (cond ((pair? y) (set! x (car y)))
            (else (+ x y))))))

(define f (adder 1))
(f 5)                 ; 6
(f (list 10))
(f 5)                 ; 15

関数とその確立されたプロトコルを使用するx以外に、それを変更する方法はありません- まさにSchemeのレキシカルスコープのためです。f

変数は、内部が定義されてxいるものに属する内部環境フレーム内のメモリ セルを参照します。つまり、"クロージャ" とも呼ばれる、 とその定義環境の組み合わせを返します。let lambdalambda

そして、この内部変数を変更するためのプロトコルを提供しない場合、それを変更することはできません。

(set! x 5) ; WRONG: "x", what "x"? it's inaccessible!

編集:あなたの質問の意味を完全に変えるあなたの新しいコードは、そこにも問題はありません。私たちはまだその定義環境の中にいるようなものなので、当然、内部変数には引き続きアクセスできます。

さらに問題なのは以下

(define x 1)
(define (f y) (+ x y))
(define x 4)
(f 5) ;?? it's 9.

私は 2 番目の定義が最初の定義に干渉しないことを期待していますが、R5RS はトップレベルのdefineようなものだと言っています。set!

クロージャは、定義環境を一緒にパッケージ化します。最上位環境は常にアクセス可能です。

参照する変数xf最上位環境に存在するため、同じスコープ内の任意のコードからアクセスできます。つまり、任意のコードです。

于 2012-11-14T12:43:14.807 に答える
1

いいえ、動的スコープではありません。defineここは内部定義であり、内のコードにのみアクセスできることに注意してくださいlet。具体的にfは、モジュールレベルでは定義されていません。したがって、何も漏れていません。

内部定義は、letrec(R5RS)またはletrec*(R6RS)として内部的に実装されます。したがって、(R6RSセマンティクスを使用している場合)次のように扱われます。

(let ((x 1))
  (letrec* ((f (lambda (y) (+ x y))))
    (set! x 2)
    (f 3)))
于 2012-11-14T01:12:13.077 に答える
1

私の答えは明らかですが、他の誰もそれに触れていないと思うので、言わせてください。はい、怖いです. ここで実際に観察しているのは、突然変異によって、プログラムが何をしようとしているのかを推論するのが非常に難しくなっているということです。純粋に機能するコード (変更のないコード) は、同じ入力で呼び出された場合、常に同じ結果を生成します。状態とミューテーションを含むコードには、このプロパティがありません。同じ入力で関数を 2 回呼び出すと、別の結果が生成される場合があります。

于 2012-11-14T16:06:43.210 に答える