首先,你不需要继续。根据标准,方案将始终执行
tail call optimization
. 尾部调用是函数中处于最终位置的函数调用;运行该调用之后,将不会发生其他任何情况。在这种情况下,我们不需要保留当前所处的激活记录;只要我们调用的函数返回,我们就将其弹出。因此,尾调用重用当前激活记录。举个例子,考虑一下:
(define (some-function x y)
(preprocess x)
(combine (modified x) y))
(some-function alpha beta)
当我们呼唤
some-function
,我们在堆栈上为其激活记录分配空间:局部变量、参数等,然后调用
(preprocess x)
. 因为我们需要回到
一些函数
继续处理,我们必须保存
一些函数
的激活记录,因此我们为
preprocess
. 一旦它回来,我们就跳
预处理
的堆栈框架并继续。接下来,我们需要评估
modified
同样的事情也会发生,而且
被改进的
返回,其结果传递给
combine
. 有人会认为我们需要创建一个新的激活记录,运行
结合
,然后将此返回到
一些函数
-但是
一些函数
不需要对结果做任何事情,只需要返回它!因此,我们覆盖当前的激活记录,但只保留返回地址;当
结合
然后,它将返回其值,精确到等待它的值。在这里,
(combine (modified x) y)
是一个尾调用,评估它不需要额外的激活记录。
这就是如何在方案中实现循环,例如:
(define (my-while cond body)
(when (cond)
(body)
(my-while cond body)))
(let ((i 0))
(my-while (lambda () (< i 10))
(lambda () (display i) (newline) (set! i (+ i 1)))))
如果没有尾部调用优化,这将是低效的,并且可能会由于长时间运行的循环而溢出,从而导致大量调用
my-while
. 但是,由于尾调用优化,递归调用
my-while cond body
是一个跳转,不分配内存,使其与迭代一样高效。
其次,这里不需要任何宏。而你可以抽象出
display
块,您可以使用普通函数执行此操作。宏允许您在某种程度上更改语言的语法添加您自己的
define
实现一些不计算其所有分支的类型case构造等。当然,它仍然是S表达式,但语义不再是简单的“计算参数和调用函数”。然而,在这里,您只需要函数调用语义。
有了这句话,我想这就是我实现代码的方式:
(require (lib "string.ss"))
(define (print-report width . nvs)
(if (null? nvs)
(void)
(let ((name (car nvs))
(value (cadr nvs)))
(display (format "~a:~a $~a~n"
name
(make-string (- width (string-length name) 2) #\-)
(real->decimal-string value 2)))
(apply print-report width (cddr nvs)))))
(define (ab-income)
(display "Income: ")
(let ((income (string->number (read-line))))
(if (or (not income) (<= income 600))
(begin (display "Please enter an amount greater than $600.00\n\n")
(ab-income))
(begin (newline)
(print-report 40 "Deduct for bills" (* 3/10 income)
"Deduct for taxes" (* 2/10 income)
"Deduct for savings" (* 1/10 income)
"Remainder for checking" (* 4/10 income))))))
首先,至少在我的版本中
mzscheme
我需要一个
(require (lib "string.ss"))
导入线
real->decimal-string
. 接下来,我抽象出
显示
阻止你的谈话。我们看到的是,每行都希望在第40列以相同的格式打印钱,在钱前面打印一个标记名和一行破折号。因此,我写道
print-report
. 第一个参数是初始宽度;在本例中,
40
. 其余参数是字段值对。每个字段的长度(加上两个冒号和空格)从宽度中减去,我们生成一个由许多破折号组成的字符串。我们使用
format
按正确的顺序排列田地,以及
显示
打印字符串。函数在所有对上递归(使用尾部递归,这样我们就不会破坏堆栈)。
在主功能中,我移动了
(display "Income: ")
之前
let
;您忽略了它的结果,那么为什么要将它赋给一个变量呢?然后我增加了
if
测试条件
input
是假的,当
string->number
无法分析输入。最后,我删除了您的局部变量,因为您只需要打印它们,并使用Scheme的分数语法而不是除法。(当然,我用
打印报告
而不是
显示
S和
格式
美国)
我想就这些了;如果你对我所做的有任何其他问题,可以问。