Ixm*_*tus 5 scheme continuations racket
自从我触及Scheme并决定使用Scheme实现命令行收入分配器已经有几个月了.
我的初始实现使用了对延续的简单递归,但我认为延续更适合这种类型的程序.我很感激,如果有人(比我更熟练的方案)可以看看这个并提出改进建议.我是多(display...行也是使用宏的理想机会(我还没有得到宏).
(define (ab-income)
(call/cc
(lambda (cc)
(let
((out (display "Income: "))
(income (string->number (read-line))))
(cond
((<= income 600)
(display (format "Please enter an amount greater than $600.00~n~n"))
(cc (ab-income)))
(else
(let
((bills (* (/ 30 100) income))
(taxes (* (/ 20 100) income))
(savings (* (/ 10 100) income))
(checking (* (/ 40 100) income)))
(display (format "~nDeduct for bills:---------------------- $~a~n" (real->decimal-string bills 2)))
(display (format "Deduct for taxes:---------------------- $~a~n" (real->decimal-string taxes 2)))
(display (format "Deduct for savings:-------------------- $~a~n" (real->decimal-string savings 2)))
(display (format "Remainder for checking:---------------- $~a~n" (real->decimal-string checking 2))))))))))
Run Code Online (Sandbox Code Playgroud)
调用(ab-income)请求输入,如果提供低于600的任何东西,它(从我的理解)返回(ab-income)到current-continuation.我的第一个实现(正如我之前所说)使用了简单的递归.这一切都不错,但是(ab-income)如果价值低于600则继续扩大功能我想到每次回复.
(如果这种担心不正确,请纠正我!)
Ant*_*sky 17
首先,您不需要延续.根据标准,Scheme将始终执行尾调用优化.尾调用是函数调用,它位于函数的最终位置; 在该呼叫运行之后,不会发生任何其他事情.在这种情况下,我们不需要保留我们目前所处的激活记录; 一旦我们调用的函数返回,我们就会弹出它.因此,尾调用重用当前激活记录.举个例子,考虑一下:
(define (some-function x y)
(preprocess x)
(combine (modified x) y))
(some-function alpha beta)
Run Code Online (Sandbox Code Playgroud)
当我们调用时some-function,我们在堆栈上为其激活记录分配空间:局部变量,参数等.然后我们调用(preprocess x).由于我们需要返回some-function并继续处理,我们必须保留some-function激活记录,因此我们推送新的激活记录preprocess.一旦返回,我们弹出preprocess堆栈框架继续前进.接下来,我们需要评估modified; 同样的事情必须发生,并在modified返回时,其结果传递给combine.有人会认为我们需要创建一个新的激活记录,运行combine,然后将其返回some-function- 但some-function不需要对该结果做任何事情但返回它!因此,我们覆盖当前的激活记录,但只留下返回地址; 当combine返回时,它会将其值返回到正在等待它的值.这里(combine (modified x) y)是一个尾调用,并且评估它不需要额外的激活记录.
这是你在Scheme中实现循环的方法,例如:
(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)))))
Run Code Online (Sandbox Code Playgroud)
如果没有尾调用优化,这将是低效的,并且可能会在长时间运行的循环中溢出,从而构建大量调用my-while.但是,由于尾调用优化,递归调用my-while cond body是一个跳转,并且不分配内存,这使得它与迭代一样高效.
其次,这里不需要任何宏.虽然您可以抽象出display块,但您可以使用普通函数执行此操作.宏在某种程度上允许你改变语言的语法 - 添加你自己的类型define,实现一些不评估其所有分支的类型案例构造,等等.当然,它仍然是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))))))
Run Code Online (Sandbox Code Playgroud)
首先,至少在我的版本中mzscheme,我需要(require (lib "string.ss"))一行来导入real->decimal-string.接下来,我抽出display你正在讨论的块.我们看到的是,每一行都希望在第40列以相同的格式打印货币,在其前面打印标签名称和一行破折号.因此,我写道print-report.第一个参数是初始宽度; 在这种情况下,40.其余参数是字段 - 值对.从宽度中减去每个字段的长度(加上冒号和空格的两个字段),我们生成一个由多个破折号组成的字符串.我们使用format以正确的顺序排列字段,并display打印字符串.该函数对所有对进行递归(使用尾递归,因此我们不会对堆栈进行打击).
在主要功能中,我移动(display "Income: ")到之前let; 你忽略了它的结果,为什么要把它分配给变量?然后我扩充if条件以测试if是否input为false,这在string->number无法解析输入时发生.最后,我删除了你的局部变量,因为你所做的只是打印它们,并使用Scheme的分数语法而不是除法.(当然,我使用print-report而不是displays和formats.)
我认为这就是全部; 如果您对我所做的事有任何其他疑问,请随时提出.