寻求有关 SICP 练习 1.5 的一些解释

问题描述 投票:0回答:2

问题可以在这里找到。

在书中,我发现对正常订单评估的描述是:

“另一种评估模型在需要操作数的值之前不会评估操作数。相反,它会首先用操作数表达式替换参数,直到获得仅涉及原始运算符的表达式,然后执行评估。”

我还发现了另外一个简短的描述:“充分展开然后缩小”。

在练习中,我认为

p
的定义类似于
(lambda () (p))
, 它永远不会扩展到原始运算符,因此永远不会终止。

然而,另一方面,在谷歌上搜索了这个问题的一些答案后,似乎正常的顺序评估应该终止,因为它只根据需要评估事物,实际上

(p)
不会被评估。

所以我认为“扩展”和“评估”之间一定有一些区别,解释器在这里所做的是评估事物。

到底有什么区别,或者 我是否遗漏了一些要点?

另一个问题:我应该说“

(p)
被评估为
(p)
”还是“
(p)
被扩展到
(p)
”?

lisp scheme sicp
2个回答
25
投票

你是对的,如果要求评估

(p)
,应用顺序评估器不会终止。然而,当前的问题提到
if
具有由应用和正常顺序求值器共享的特定求值语义。具体来说:

假设无论解释器使用普通顺序还是应用顺序,特殊形式 if 的求值规则都是相同的:首先对谓词表达式进行求值,结果决定是否对后件表达式或替代表达式求值。

练习中的代码是

(define (p) (p))

(define (test x y)
  (if (= x 0)
    0
    y))

正在考虑的测试是

(test 0 (p))

正序评估是“完全扩展然后减少”选项。在正常顺序评估下,

(test 0 (p))
完全展开为

(test 0 (p)) ==
(if (= 0 0)
  0
  (p))

由于

if
具有上述语义,并且扩展中的测试条件是
(= 0 0)
,这是 true,所以正序求值器决定对后件进行求值,即
0
,因此表达式的值是
0

然而,使用应用顺序求值,求值

(test 0 (p))
的第一步是求值表达式
test
0
(p)
,然后调用 ("apply",因此 "applicative ")
test
的值以及通过评估
0
(p)
产生的值。由于
(p)
的评估不会完成,因此
(test 0 (p))
的评估也不会完成。


5
投票

在正常的求值规则下,

(p)
将通过调用不带参数的函数
p
来求值。例如(在 Common Lisp 中):

> (defun p ()
    5)
  => P
> (p)
  => 5

您的问题首先提到了“惰性评估”。 Common Lisp 默认情况下不这样做;它从左到右评估函数的所有参数。方案没有指定它们的评估顺序,只是说明了它们的评估顺序。

然而,在事物被求值之前,它们需要被扩展(这在 lisp 中可能意味着很多事物),这使得 lisp 能够控制求值的顺序。例如,

p
可以是一个宏。在这种情况下,正常的评估规则不一定适用。再次,在 Common Lisp 中:

> (defmacro p ()
    (print "I'm being expanded!") ; print on expansion
    (terpri) ; new line.
    `(progn (print "I'm being evaluated!") ; print on evaluation
            (terpri)
            5))
  => P

这将进入读取评估打印循环。读取表达式,然后展开、求值,然后打印。

> (p)
  I'm being expanded!
  I'm being evaluated!
  => 5

要停止计算扩展,我们将其放入 lambda 中。您会注意到它仍然打印扩展消息。

> (defvar foo (lambda () (p)))
  I'm being expanded!
  => COMPILED FUNCTION #<LAMBDA>

现在我们调用它,并对表单进行评估。

> (funcall foo) ; call the function 
  I'm being evaluated!
  => 5

您可以自行扩展宏调用。

> (macroexpand-1 '(lambda () (p)))
  I'm being expanded!
  => (lambda () (progn (print "I'm being evaluated!")
                       (terpri)
                       5))

Haskell 等语言默认具有惰性求值。在您引用的段落中,SICP 让您想象了一个惰性版本的 lisp。为了让这样的 lisp 工作,并且只评估需要的东西,它不仅需要盲目地扩展和评估所有内容,直到达到一个值(请参阅 SICP 中对替换模型的讨论),而是只扩展事物并评估仅在特别询问事物的价值时才评估事物。您可以将其与上面的示例进行比较,在上面的示例中,我们在 lambda 表达式主体中扩展了宏

P
,当我们需要值时强制使用
FUNCALL
进行求值。稍后在 SICP 中,您将使用类似的技术来实现惰性列表。

正如我所说,Haskell 会自动执行此类操作,并且不难想象 lisp 会执行相同的操作(尽管目前还没有流行的 Lisp 这样做)。考虑到书中的实际问题,我想我的说法有点离题,但希望您能够了解如何判断评估的内容和评估的时间,以及它可以产生的差异。

© www.soinside.com 2019 - 2024. All rights reserved.