2

私は実際に楽しみのために本を読んでいますが、それは宿題と見なされるかもしれません。いずれにせよ、私はこの言語ではローカル状態変数にまったく慣れていません...たとえば次のコードを見てください。

(define flip
  (let ((count 0))
    (lambda ()
      (if (= 0 count)
          (begin (set! count 1) count)
          (begin (set! count 0) count)))))

このコードが1と0の間で交互になるのはなぜですか?この関数が呼び出されるたびに、countには0の値が与えられます。Pythonに相当するものは次のとおりです。

class Flip:
    def __init__(self):
        pass
    def __call__(self):
        count = 0
        if count == 0:
            count = 1
            return count
        else:
            count = 0
            return count

これは毎回同じものを返します。よくわかりません...

4

6 に答える 6

4

私は関数型言語用のコンパイラーを書いた経験が少しあるので、おそらくその関数がどのようにメモリーに格納/表現されるかについての簡単な説明が正しいでしょう。すべての関数は、大まかにペア(E、F)と考えることができます。ここで、Eは自由変数のセットであり、Fは関数自体の「コード」です。関数を呼び出すと、Eの値を取得し、Fの変数の代わりにそれらの値を使用して、それらの値を使用してコードを実行します。

したがって、例に関しては、変数「flip」をlet式によって返される関数として定義しました。その関数はラムダ内のものです。「count」はラムダの外部で定義されているため、自由変数であり、関数の環境に格納されます。次に、(flip)を呼び出すたびに、インタープリターはラムダ内のコードに移動し、環境内の「count」の値を検索する必要があることを確認し、それを実行して変更し、戻ります。そのため、呼び出すたびに、「count」に格納されている値が保持されます。

フリップを呼び出すたびにカウントをゼロにしたい場合は、let式をラムダ内に配置して、自由変数ではなく束縛変数にします。

于 2009-07-23T19:23:09.480 に答える
3

ラムダはクロージャです。これは、自由変数(count)を参照する関数であり、ローカルで定義されていないか、パラメーターの1つではなく、最も近い囲んでいる字句環境にバインドされています。

呼び出される関数はラムダであり、「フリップ」ではありません。Flipは、(let ...)式から返されるラムダに付けた名前です。

Pythonに関しては、言語はわかりませんが、countは、呼び出すローカル変数ではなく、Flipオブジェクトのメンバーである必要があるようです。

于 2009-07-23T06:02:34.520 に答える
2

それはもっと似ています

class Flip:
    def __init__(self):
        self.count = 0
    def __call__(self):
        if self.count == 0:
            self.count = 1
            return self.count
        else:
            self.count = 0
            return self.count

より多くの説明で更新:count Schemeの関数は、その外側のスコープで定義されて いる自由変数の周りを「閉じる」クロージャです。count関数だけを本体としてaで定義されている方法letは、関数がそれにアクセスできる唯一のものであることを意味します。つまりcount、関数にアタッチされた一種のプライベートな可変状態を効果的に作成します。

これは、SICPのSchemeで従来から「オブジェクト」を作成する方法です。つまり、一連letの変数(インスタンス変数、初期値に初期化)を定義し、本体で「メソッド」である一連の関数を定義します。インスタンス変数へのアクセスを共有している。countそのため、ここでは、インスタンス変数として、Pythonクラスを使用して何が起こっているかを表すのが自然です。

Python 3.xへのより直訳は、次のようになります(Pythonにはlet(限定スコープのローカル変数宣言)構文がないため、概算にすぎないことに注意してください。Pythonのlambdasは使用できないため、使用できません。 tテイクステートメント):

count = 0

def flip():
    nonlocal count
    if count == 0:
        count = 1
        return count
    else:
        count = 0
        return count

# pretend count isn't in scope after this
于 2009-07-23T06:29:41.657 に答える
2

フリップ関数は実際には関数(ラムダ内で定義されている)を返すためです

返された関数を呼び出すたびに、その環境が変更されます。

あなたがそれについて考えるならば、letは環境を一度だけ作成します(そしてカウントを0に初期化します)-ラムダ関数があなたに返されるとき。

ある意味で、ラムダは環境を使用する関数オブジェクトを作成します。その最後のフレームは、単一の変数カウントでletで初期化されました。関数を呼び出すたびに、関数はその環境を変更します。もう一度flipを呼び出すと、環境が異なる別の関数オブジェクトが返されます。(カウントは0に初期化されます)次に、2つのファンクターを個別に切り替えることができます。

それがどのように機能するかを完全に理解したい場合は、環境モデルについて読む必要があります。

于 2009-07-23T05:55:25.753 に答える
1

元のコードの問題は、命令型のスタイルに強い影響を与えることです。より慣用的な解決策は次のとおりです。

(define (flip)
  (let ((flag #t))
    (lambda ()
      (set! flag (not flag))
      (if flag 1 0))))
于 2009-07-23T06:07:18.350 に答える
0

コメントoobooの質問に答えるには、関数を返す関数が必要です

(define make-flipper
  (lambda ()
    (let ((count 0))
      (lambda ()
    (let ((v count))
      (set! count (- 1 count))
      v)))))

;; test it
(let ((flip-1 (make-flipper)))
  (format #t "~s~%" (flip-1))  
  (format #t "~s~%" (flip-1))
  (format #t "~s~%" (flip-1))

  (let ((flip-2 (make-flipper)))
    (format #t "~s~%" (flip-2))
    (format #t "~s~%" (flip-2))
    (format #t "~s~%" (flip-2))))

セットを簡単に変更できます!フリッパーではなく、カウンターになるように線を引きます(より便利です)。

于 2009-07-30T13:00:21.647 に答える