scheme使用let / set“记住”值的函数

时间:2012-05-31 17:09:24

标签: scheme closures mutators lexical-scope

我是Scheme的新手,并试图了解函数中出现的某些值如何在多次使用中持续存在。采取以下计数器:

(define count
   (let ((next 0))
     (lambda ()
       (let ((v next))
         (set! next (+ next 1))
         v))))

我无法弄清楚(并且未在任何地方找到解释),这就是为什么next每次使用count时都不会重置为0。

6 个答案:

答案 0 :(得分:12)

这称为闭包。整个程序中只有next的一个版本。

为了更清楚,请考虑以下程序:

(define next 0)

(define count
  (lambda ()
    (let ((v next))
      (set! next (+ next 1))
      v))))

现在很清楚,只有一个next

您编写的版本不同,因为您已使用let确保只有lambda表达式才能看到next。但是仍然只有一个next。如果您将其更改为此,请改为:

(define count
  (lambda ()
    (let ((next 0))
      (let ((v next))
       (set! next (+ next 1))
       v))))

然后你每次都会创建一个新版本的next,因为next的声明是里面的 lambda,这意味着它每次都会发生调用lambda的时间。

答案 1 :(得分:5)

我有一件事要添加到Sam的优秀答案:你的问题表明这种行为可能与“让”有关。它不是。这是一个在没有“let”的情况下做类似事情的例子:

#lang racket

(define (make-counter-from counter)
  (lambda ()
    (set! counter (+ counter 1))
    counter))

(define count (make-counter-from 9))

(count)
(count)

道德(如果有的话):是的!突变令人困惑!

编辑:根据您在下面的评论,听起来您真的在寻找一些洞察您可以用于具有突变的语言的心理模型。

在具有局部变量变异的语言中,您不能使用用值替换参数的简单“替换”模型。相反,每次调用函数都会创建一个新的“绑定”,以后可以更新(a.k.a。“mutated”)。

因此,在上面的代码中,使用9调用“make-counter-from”会创建一个新的绑定,将“counter”变量与值9相关联。然后附加/替换此绑定 - 用于“计数器“函数体中的变量,包括lambda内部的变量。函数的结果是一个lambda(一个函数),它“关闭”对这个新创建的绑定的两个引用。如果您愿意,可以将这些视为对堆分配对象的两个引用。这意味着对结果函数的每次调用都会导致对此对象/堆事物的两次访问。

答案 2 :(得分:2)

我不完全同意你的解释。你是对的,函数的定义只被评估一次,但每次调用函数本身都会被评估。

我不同意的一点是“...重写定义......”,因为该函数只定义了一次(并没有明确覆盖)。

我想象它的方式如下:由于方案中所谓的变量词法绑定,方案解释器在评估函数定义时注意到在函数定义的环境中定义了一个变量 - 变量“下一个”。因此,它不仅记住函数定义,还记住变量“next”的值(这意味着它存储了两个东西 - 函数定义和封闭环境)。当第一次调用函数时,其定义由存储环境中的方案解释器评估(其中变量“next”具有值0,并且值是递增的)。第二次调用函数时,完全相同的事情发生 - 在其封闭环境中评估相同的函数定义。但是,这次,环境为变量“next”提供值1,函数调用的结果为1。

短语很简洁:函数(定义)保持不变,它是更改的评估环境。

答案 3 :(得分:2)

要直接回答您的问题,“next每次使用0时都不会重置为count,因为您的代码

(define count (let ((next 0))
                 (lambda ()
                    (let ((v next))
                       (set! next (+ next 1))
                       v))))

等同于

(define count #f)

(set! count ( (lambda (next)              ; close over `next`
                 (lambda ()                  ; and then return lambda which _will_
                    (let ((v next))          ;   read from `next`,
                       (set! next (+ v 1))   ;   write new value to `next`,
                       v)))                  ;   and then return the previous value;
              0 ))                        ; `next` is initially 0

(这是“let-over-lambda”模式;甚至还有一个名称的Lisp书。)

分配count的值仅“计算”一次。这个值是一个闭包,指的是next的绑定,(绑定)在它的外部(闭包)。然后,每次count被“使用”,即它所引用的过程被调用时,它(过程)引用该绑定:首先它从中读取,然后它改变其内容。但它没有将其重新设定为初始值;作为创建的一部分,绑定仅启动一次,这在创建闭包时发生。

此绑定仅在此过程中可见。闭包是这个过程的捆绑和持有这种绑定的环境框架。此闭包是评估(lambda () ...) next表达式的{em>词法范围内的(lambda (next) ...)表达式的结果。

答案 4 :(得分:0)

好的,我有一些顿悟。我相信我的困惑与定义和过程(lambda)之间的区别有关:定义发生一次,而程序每次运行时都会评估。在原始函数中,let定义了next设置为零的过程。该定义发生了一次,但在过程中使用set!会重写定义,就像追溯一样。因此,每次使用count都会生成函数的新版本,其中{ {1}}已增加。

如果这完全偏离基础,请纠正我。

答案 5 :(得分:0)

这是一个完全相同的程序。

(define count
  (local ((define next 0)          
          (define (inc)
            (local ((define v next))
                    (begin 
                      (set! next (+ next 1))
                      v))))
    inc))

 (count)   0
 (count)   1
 (count)   2 .........

也许你的let / set序列遵循相同的机制。您实际调用的过程是inc,而不是计数。 inc程序每次都会增加 next 。等价定义可以是

 (define next 0)

 (define (inc)
  (begin
    (set! next (+ next 1))
    (- next 1)))

 (define count inc)


 (count)  0
 (count)  1
 (count)  2......

所以,我想在第一个程序中调用count就像运行整个第二个程序一样。我不知道。我也不熟悉计划。谢谢,有用的帖子。