我在Structure and Interpretation of Computer Programs的练习中一直在慢慢地练习。第1.1.5节讨论了应用程序与正常顺序评估,之后在文本中出现了几次主题。由于解释器使用了应用程序顺序评估,因此很容易在代码中插入display
调试语句,以确切了解它是如何工作的。对于我的理解,能够为正常的订单评估做同样的事情。
有没有人知道使用正常顺序评估而不是应用顺序实现的Scheme(或Lisp)解释器?
这是一个简短的例子,修改自SICP中给出的一个例子。我将定义自己的add
过程来打印出参数,并使用书中的square
过程。
(define (add x y)
(display x)
(display y)
(newline)
(+ x y))
(define (square x) (* x x))
现在,如果我使用applicative-order评估运行短程序(square (add 1 2))
,(add 1 2)
的结果将只计算一次,然后传递给square
程序。操作数12
应在最终结果之前打印一次。我们可以在解释器中运行它来验证这是发生了什么。
> (square (add 1 2))
12
9
但是,使用正常顺序评估时,应将单个操作数(add 1 2)
复制到square
过程中,该过程将评估为(* (add 1 2) (add 1 2))
。操作数12
应在最终结果之前打印两次。
我希望能够在执行正常顺序评估的解释器中运行它,以验证它确实是如何工作的。
答案 0 :(得分:6)
Racket有一个lazy language。它不仅仅是一个解释器,因为你可以编写由普通球拍模块和懒人组成的程序。
至于使用打印输出进行调试 - 您可以使用这种惰性语言进行调试,并在Haskell中获得类似于不安全IO的内容。尽管如此,这仍然会令人困惑。 (如果你想要一个翻译器将打印输出插入其中,那么也会让人感到困惑,因为它遵循了懒惰的评估......)
答案 1 :(得分:6)
事实证明,Scheme实际上已经是一个基本上是正常的评估者。它们是你可能已经听过的那些传说中的宏,我们可以重写1.1.4--1.5.5节的例子来代替程序应用程序而不是轻松地使用宏扩展:
(define (print . items) (for-each display items)) (define-macro (add x y) `(begin (print "[ADD " ',x " " ',y "]") (+ ,x ,y))) (define-macro (mul x y) `(begin (print "[MUL " ',x " " ',y "]") (* ,x ,y))) (define-macro (square x) `(begin (print "[SQUARE " ',x "]") (mul ,x ,x))) (define-macro (sum-of-squares x y) `(begin (print "[SUM-OF-SQUARES " ',x " " ',y "]") (add (square ,x) (square ,y)))) (define-macro (f a) `(begin (print "[F " ',a "]") (sum-of-squares (add ,a 1) (mul ,a 2))))
忽略PRINT,他们的逻辑有点超出你在文本中的位置,但它们只是许多DISPLAY的简写。实际上,您可能希望完全放弃打印跟踪,转而使用系统的宏扩展功能。但这随实施而变化(例如在Ypsilon中你会使用(macro-expand '(f 5))
)。
如果你加载这些定义(但需要注意的是DEFINE-MACRO是非标准的,但实际上这不应该是一个问题,因为大多数方案都提供了它),那么像书一样评估(f 5)
会打印出来(当然我把它打扮得有点):
[F 5] [SUM-OF-SQUARES (add 5 1) (mul 5 2)] [ADD (square (add 5 1)) (square (mul 5 2))] [SQUARE (add 5 1)] [MUL (add 5 1) (add 5 1)] [ADD 5 1] [ADD 5 1] [SQUARE (mul 5 2)] [MUL (mul 5 2) (mul 5 2)] [MUL 5 2] [MUL 5 2] 136
这或多或少是本书所说明的过程应该是什么。
编写这些类型的宏基本上就像编写正常的过程一样,除了
那就是写作方案宏101。
现在总而言之,在SICP的第一章中,对某人来说,这是一个有点傻的东西。但是,如果你说你在修改Racket做你想要的事情时遇到了太大的困难(然后有些人根本没有使用Racket),那么这里就是另一种选择。