我是 Scheme 的新手,并试图了解函数中出现的某些值如何在多种用途中持续存在。采取以下柜台:
(define count
(let ((next 0))
(lambda ()
(let ((v next))
(set! next (+ next 1))
v))))
我无法弄清楚(并且在任何地方都没有找到解释),是为什么next
每次使用时都不会重置为 0 count
。
我是 Scheme 的新手,并试图了解函数中出现的某些值如何在多种用途中持续存在。采取以下柜台:
(define count
(let ((next 0))
(lambda ()
(let ((v next))
(set! next (+ next 1))
v))))
我无法弄清楚(并且在任何地方都没有找到解释),是为什么next
每次使用时都不会重置为 0 count
。
这称为闭包。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
被调用时都会发生。
我有一件事要补充到 Sam 的出色回答:您的问题表明这种行为可能与“让”有关。它不是。这是一个没有“let”的例子,它做了类似的事情:
#lang racket
(define (make-counter-from counter)
(lambda ()
(set! counter (+ counter 1))
counter))
(define count (make-counter-from 9))
(count)
(count)
道德(如果有的话):是的!突变令人困惑!
编辑:根据您在下面的评论,听起来您确实在寻找一些洞察力,以了解您可以将哪种心理模型用于具有突变的语言。
在具有局部变量变异的语言中,您不能使用将参数替换为值的简单“替换”模型。相反,对函数的每次调用都会创建一个新的“绑定”,以后可以对其进行更新(也称为“变异”)。
因此,在我上面的代码中,使用 9 调用“make-counter-from”会创建一个新绑定,该绑定将“counter”变量与值 9 相关联。然后附加/替换该绑定以用于“counter”变量的所有使用在函数体中,包括那些在 lambda 内部的。该函数的结果是一个 lambda(一个函数),它“关闭”了对这个新创建的绑定的两个引用。如果您愿意,可以将它们视为对堆分配对象的两个引用。这意味着对结果函数的每次调用都会导致对该对象/堆事物的两次访问。
我不完全同意你的解释。你是对的,函数的定义只被评估一次,但函数本身每次被调用时都会被评估。
我不同意的一点是“......重写定义......”,因为该函数只定义一次(并且没有明确覆盖)。
我想象它是这样的:由于scheme中所谓的变量词法绑定,scheme解释器在函数定义的评估过程中注意到在函数定义的环境中定义了一个变量——变量“next”。因此,它不仅记住函数定义,还记住变量“next”的值(这意味着它存储了两件事——函数定义和封闭环境)。当第一次调用该函数时,它的定义由存储环境中的方案解释器评估(其中变量“next”的值为 0,并且该值是递增的)。第二次调用该函数时,完全相同的事情发生 - 在其封闭环境中评估相同的函数定义。然而,这一次,环境为变量“next”提供了值 1,函数调用的结果为 1。
简而言之:功能(定义)保持不变,只是评估环境发生了变化。
要直接回答您的问题,“每次使用时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
表达式的结果(lambda (next) ...)
。
好吧,我有一些顿悟。我相信我的困惑与定义和过程( )之间的区别有关lambda
:定义发生一次,而过程每次运行时都会进行评估。在原始函数中,let
定义了一个next
设置为零的过程。该定义只发生一次,但set!
在过程中使用会重写定义,就好像追溯性一样。因此,每次使用 都会count
产生一个新版本的函数,其中next
已递增。
如果这完全偏离基础,请纠正我。
这是一个完全相同的程序。
(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,而不是 count。inc 过程每次都递增next 。一个等价的定义可以是
(define next 0)
(define (inc)
(begin
(set! next (+ next 1))
(- next 1)))
(define count inc)
(count) 0
(count) 1
(count) 2......
所以,我猜想在第一个程序中调用 count 就像运行整个第二个程序一样。我不知道。我也是新来的计划。谢谢,有用的帖子。