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

pla*_*ian 5 scheme closures lexical-scope mutators

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

(define count
   (let ((next 0))
     (lambda ()
       (let ((v next))
         (set! next (+ next 1))
         v))))
Run Code Online (Sandbox Code Playgroud)

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

Sam*_*adt 12

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

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

(define next 0)

(define count
  (lambda ()
    (let ((v next))
      (set! next (+ next 1))
      v))))
Run Code Online (Sandbox Code Playgroud)

现在很清楚,只有一个next.

你写的版本是不同的,因为你曾经let确保只有lambda表达式可以看到next.但仍然只有一个next.如果您将其更改为此,请改为:

(define count
  (lambda ()
    (let ((next 0))
      (let ((v next))
       (set! next (+ next 1))
       v))))
Run Code Online (Sandbox Code Playgroud)

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


Joh*_*nts 5

我有一件事要添加到Sam的优秀答案:你的问题表明这种行为可能与"让"有关.它不是.这是一个在没有"let"的情况下执行类似操作的示例:

#lang racket

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

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

(count)
(count)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


小智 2

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

我不同意的一点是“...重写定义...”,因为该函数仅定义一次(并且没有显式覆盖)。

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

简而言之:功能(定义)保持不变,只是评估环境发生了变化。