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 表达式返回的函数。这个函数就是你的 lambda 里面的东西。因为“count”是在 lambda 之外定义的,所以它是一个自由变量,所以它存储在函数的环境中。然后,每次调用 (flip) 时,解释器都会转到 lambda 中的代码,发现它需要在环境中查找“count”的值,然后进行更改,然后返回。这就是为什么每次调用它时,存储在“count”中的值都会持续存在。

如果您希望每次调用翻转时计数为零,请将 let 表达式放在 lambda 中,因此它是绑定变量而不是自由变量。

于 2009-07-23T19:23:09.480 回答
3

lambda 是一个闭包。它是一个引用自由变量(count)的函数,该变量不是本地定义的,也不是参数之一,而是绑定到最近的封闭词法环境。

被调用的函数是 lambda,而不是“翻转”。Flip 只是您为从 (let ...) 表达式返回的 lambda 指定的名称。

至于 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

更新更多解释: Scheme 中的函数是一个闭包,它围绕自由变量“关闭”,该变量count在其外部范围内定义。count在 a 中仅将函数作为主体定义的方式let,意味着函数是唯一可以访问它的东西——count有效地形成了一种附加到函数的私有可变状态。

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

更直接地翻译成 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

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

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

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

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

如果你想完全理解它是如何工作的,你应该阅读环境模型

于 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 回答