【问题标题】:Why does (c1) return 1, 2, 3 ect.. and not #<procedure:...make_counter.rkt:8:5>?为什么 (c1) 返回 1, 2, 3 等.. 而不是 #<procedure:...make_counter.rkt:8:5>?
【发布时间】:2016-04-12 17:01:52
【问题描述】:

这是来自环境模型部分的 SICP 课程:

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

下面,解释器说(make-counter 0)是一个过程:

> (make-counter 0)
#<procedure:...make_counter.rkt:8:5>

下面,我将c1定义为(make-counter 0)

(define c1 (make-counter 0)

下面是我对(c1) 返回计数器值而不是"procedure" 的原因感到困惑的地方。

>(c1)
1
> (c1)
2
> (c1)
3

我的思考过程是如果(c1)指向一个过程(make-counter),那么(c1)应该返回"procedure:...make_counter.rkt:8:5"

因为程序 -> 程序。

我知道应该发生什么,我只是在概念上对如何以及为什么感到困惑。

【问题讨论】:

  • 这些答案正在帮助我检查我的想法。一个难题是如何
  • 请不要忘记:为有用的答案投票,并将其中一个标记为解决方案,以便关闭。或者重新提出问题。
  • 最后,我很清楚为什么 (c1) 返回计数器。现在,它的 (make-counter 0)。手工操作,我不明白为什么不返回 n 。有人可以向我解释一下吗?

标签: scheme closures state evaluation sicp


【解决方案1】:

你的问题是什么?您是否怀疑它是否像名字所暗示的那样工作,或者您不明白它是如何工作的?

第一个30秒可以测试,第二个我来回答:

make-counter 是一个接受一个参数的过程。现在再看一下代码:它返回了什么?一个有 0 个参数的过程。

所以执行(c1) 将返回从1 向上的数字(如果从0 开始)。

为了完整性:

Gambit v4.8.1

> (define make-counter 
   (lambda (n)    
     (lambda () (set! n (+ n 1))
       n)))    
> (define c1 (make-counter 0))
> (c1)
1
> (c1)
2
> (c1)
3
> 

问题编辑后添加:

c1 是过程,但(c1)过程应用程序,在另一个编程世界中你会称之为“调用过程”。

> c1
#<procedure #2 c1>
> (c1)
1

顺便说一句,一段很好的函数式代码,如果你理解了这一点,你会以不同的方式看待编程。

更多问题通过评论:

现在,它的 (make-counter 0)。手工操作,看不懂 为什么没有返回 n。

关于c1(c1),我们给你的答案是一样的:

make-counter 返回lambda (),只有调用 lambda 时才返回 n。函数(在方案中:过程,lambdas)是可以传递的值,函数式编程原则。您必须调用它们(正确术语:应用)才能获取过程结果。

现在告诉我:如何调用方案中的过程?

更多修改

好的,我们通过用括号括起来来调用方案中的过程。

现在告诉我:(c1) 的替换步骤是什么?

【讨论】:

  • 是的,它开始对我厚厚的头骨有意义了!调用函数:用所需的参数将函数括在括号中。这将评估过程并将参数应用于它。在这种情况下,既然是环境模型,它不会完全使用替换规则,对吧?似乎需要额外的步骤,内部 lambda 封装了 n 变量,这导致它未绑定。(make-counter 0)评估并将 0 应用于 n 但返回 procedure() 而不是新的 n 值。我必须 (c1 (make-counter 0)) 返回新的 n。对吗?
  • @TrieuNguyen 关于应用程序:正确,但第二部分错误,c1 是不带参数的 lambda。关键是c1是在make-counter的环境中执行的,所以变量n在c1中是free出现的。也许你应该让这个问题休息和成熟几天,然后重新考虑。
  • 我会接受你的建议,让它休息几天,然后我会回复它。
  • 这是休息几天后的结果。以下是一些观察。我可以看到第一个问题是个人语法错误。第二个错误是方案语言中的语义错误。在重新检查您的答案后,我“认为”我从中学到了。在我看来,我想进入下一步并了解评估者应用此类程序的一般方式,这可以引导我在函数式编程永无止境的旅程中走完剩下的路。
  • 在语义错误的错误中,这个周末发生了一个“啊哈”的时刻,这位先生在讲座 7a @ 53:25 中提出了一个非常基本的问题以及教授的以下回答。 (麻省理工学院 6.001 SICP 第 7a 课)
【解决方案2】:

我的思考过程是,如果 (c1) 指向一个过程 (make-counter),那么 (c1) 应该返回“procedure:...make_counter.rkt:8:5”。

这是错误的。

这里发生的事情是c1“指向一个过程”,正如你所说,(实际上它是一个绑定到一个过程的名称)。

您应该记住,在 Scheme 中,(f e1 e2 ... en) 形式是调用过程f 的方式,将表达式e1 e2 ... en 的值传递给它。

所以,(c1)c1 完全不同:它是一种调用过程c1(没有参数)的方式。每次解释器/编译器评估(c1) 时,它都会调用过程,增加值并返回它。

【讨论】:

  • 是的,我看到我的第一个问题是我犯了第一个错误的语法。
【解决方案3】:

你有

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

所以评估make-counter

返回

   (lambda (n)    
     (lambda () (set! n (+ n 1))
       n))

并评估(make-counter 0) 只是用它的值替换该调用中的make-counter,然后继续

( (lambda (n)    
     (lambda () (set! n (+ n 1))
       n))
   0 )
=>
(let ((n 0))    
     (lambda () (set! n (+ n 1)) 
       n))

这样就创建并返回了闭包对象,

=>
(closure {environment1: ((n 0))}     ; its own binding for `n`
     (lambda () (set! n (+ n 1)) 
       n))

所以在(define c1 (make-counter 0)) 之后这个闭包就是c1 的值。

评估c1 返回其值,即上述过程(闭包)。它的打印表示是依赖于实现的(例如显示#&lt;procedure&gt; 或类似的东西)。

评估(c1)调用这个过程,

(c1)
=>
( (closure {environment1: ((n 0))}     ; its own binding for `n`
     (lambda () (set! n (+ n 1)) 
       n)) )
=>
(under {environment1: ((n 0))}
  (  (lambda () (set! n (+ n 1)) 
       n)) )

不带任何参数调用((lambda () ...) ) 只是评估其主体而不创建任何新环境,因为参数列表为空且没有参数:

=>
(under {environment1: ((n 0))}
    (set! n (+ n 1))             ; perform this first
    n )
=>
(under {environment1: ((n 1))}   ; <--- altered binding for `n`!
    n )                          ; now evaluate this and return its value
=>
1

它离开了改变的 environment1。当再次调用该过程(闭包)时,(c1),其环境现在将包含((n 1)),因此将更改为((n 2)),等等。

【讨论】:

  • 解释额外的规则真的有助于我的理解/焦虑哈哈。它仍然使用替换规则,但由于它创建了一个环境,解释器必须做一些额外的步骤——除非我不必明确地写这个。它是否正确?引擎盖下还有更多内容(正如您所解释的)。从某种意义上说,这是否属于特殊形式或语法糖的范畴,我们真的不必显式运行代码行,而我只需要定义 make-counter 和 c1?
  • 想象一下,Scheme 确实在内存中创建了真实的对象,由对和符号构成——即符号表达式——参见www-formal.stanford.edu/jmc/recursive.ps。研究这篇论文(以及 SICP 的 mceval 章节)对我很有帮助。也可以看看this answer of mine 有没有澄清(如果没有,不要花太多力气,用经典)。 ——所以这个评估过程是非常具体和明确的,它实际上操纵了内存中的那些标记列表。了解了这一点后,我们可以继续进行更复杂的操作
  • ...解释/编译模式。 ——不,那里没有特殊形式。当我写(under {environment1: ... 时,它就像一个伪代码。但它可以实现为内存中的实际标记列表(并且“environment1”是内存中此类对象的名称,具有特定的内存地址 - 变量名称及其值的列表,即环境)。请参阅我提到的另一个答案,它用一些更易读的(对我而言)伪代码重写了该论文中的代码。 (更正:那里提到的书中的代码,而不是纸质的;但它们很接近)。一定要研究论文中的代码。
  • 还有这个paper by Wadler 批评 Lisp 并赞扬 SASL/Miranda/Haskell 语法。从多个角度看东西是件好事。 :)
  • @WillNess。哇,伙计,这太棒了——你给我的这些论文和链接是非常密集的阅读材料(至少对我来说)。特别是“符号表达式的递归函数及其机器计算,第一部分”。我必须把它切成一口大小的块,但值得熟悉它。感谢您的资源,因为它详细介绍了这个和其他概念背后的数学!!!
猜你喜欢
  • 2019-01-09
  • 1970-01-01
  • 1970-01-01
  • 2015-04-08
  • 2023-01-16
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-10
相关资源
最近更新 更多