小SICP /计划问题(当地州)

时间:2009-07-23 05:49:54

标签: scheme sicp

我实际上是为了好玩而读这本书,但可能会被视为家庭作业。无论如何,我对使用这种语言的本地状态变量感到不舒服......比如这段代码:

(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

每次都会返回相同的内容。我很困惑......

6 个答案:

答案 0 :(得分:4)

我在为函数式语言编写编译器方面有一些经验,所以可能有一个关于如何在内存中存储/表示该函数的简短描述。每个函数大致可以被认为是一对(E,F),其中E是自由变量的集合,F是函数本身的“代码”。当你调用函数时,它接受E中的值并用F代替变量中的值,然后使用这些值执行代码。

因此,在您的示例中,您已将变量“flip”定义为let表达式返回的函数。该函数是lambda中的内容。因为“count”是在lambda之外定义的,所以它是一个自由变量,所以它存储在函数的环境中。然后,每次调用(翻转)时,解释器转到lambda中的代码,看到它需要在环境中查找“count”的值,这样做,更改它,然后返回。这就是为什么每次调用它时,存储在“count”中的值仍然存在。

如果你希望每次调用flip时count都为零,那么将let表达式放在lambda中,这样它就是一个绑定变量而不是一个自由变量。

答案 1 :(得分:3)

lambda是一个闭包。它是一个引用自由变量(count)的函数,它不是本地定义的,也不是其中一个参数,它与最接近的封闭词汇环境绑定。

被调用的函数是lambda,而不是“flip”。翻转只是你给lambda的一个名字,它是从(let ...)表达式返回的。

对于Python,我不知道语言,但看起来count应该是Flip对象的成员,而不是调用的本地变量。

答案 2 :(得分:2)

因为你的翻转函数实际上返回一个函数(在lambda中定义)

每次调用返回的函数时,它都会修改其环境。

如果您考虑一下,只创建一次环境(并将计数初始化为0) - 当lambda函数返回给您时。

从某种意义上说,lambda为你创建了一个使用环境的函数对象,它的最后一帧在 let 中初始化,只有一个变量count。每次调用函数时,它都会修改其环境。 如果再次调用 flip ,则返回另一个具有不同环境的函数对象。 (计数初始化为0)然后,您可以单独切换两个仿函数。

如果您想完全了解其工作原理,请阅读环境模型

答案 3 :(得分: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

更新更多解释 Scheme中的函数是一个闭包,它在自由变量count周围“关闭”,它在它之外的范围内定义。 countlet中定义只有函数作为正文的方式,意味着该函数是唯一可以访问它的函数 - 使count实际上成为一种私有附加到函数的可变状态。

传统上在SICP中的Scheme中创建“对象”的方式 - 让let定义一堆变量(实例变量,初始化为它们的初始值),并在body中定义一个一堆函数,它们是对实例变量具有共享访问权限的“方法”。这就是为什么在这里使用Python类来表示正在发生的事情是很自然的,count是一个实例变量。

对Python 3.x的更直接的翻译将是这样的(注意它只是近似的,因为Python没有let(有限范围的局部变量声明)语法和Python的{{ 1}} s不能使用,因为它们不接受语句):

lambda

答案 4 :(得分:1)

原始代码的问题在于它对命令式风格有很强的影响力。一个更惯用的解决方案是:

(define (flip)
  (let ((flag #t))
    (lambda ()
      (set! flag (not flag))
      (if flag 1 0))))

答案 5 :(得分: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))))

您可以轻松更改套装!线,使它成为一个计数器,而不是一个鳍状肢(更有用)。