实用方案编程
自从我接触Scheme并决定使用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))))))))))
调用 (ab-venue)
要求输入,如果提供了低于 600 的值(根据我的理解),它会在 current-continuation
处返回 (ab-venue)
。正如我之前所说)使用普通简递归也不错,但我认为如果值低于 600,则每次返回调用都会继续扩展函数
( 。如果这种担心不正确,请纠正我!)
It's been a few months since I've touched Scheme and decided to implement a command line income partitioner using Scheme.
My initial implementation used plain recursion over the continuation, but I figured a continuation would be more appropriate to this type of program. I would appreciate it if anyone (more skilled with Scheme than I) could take a look at this and suggest improvements. I'm that the multiple (display...
lines is an ideal opportunity to use a macro as well (I just haven't gotten to macros yet).
(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))))))))))
Invoking (ab-income)
asks for input and if anything below 600 is provided it (from my understanding) returns (ab-income)
at the current-continuation
. My first implementation (as I said earlier) used plain-jane recursion. It wasn't bad at all either but I figured every return call to (ab-income)
if the value was below 600 kept expanding the function.
(please correct me if that apprehension is incorrect!)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
首先,你不需要继续。根据标准,Scheme 将始终执行尾调用优化。尾调用是位于函数最终位置的函数调用;运行该调用后,不会发生任何其他事情。在这种情况下,我们不需要保留当前的激活记录;一旦我们调用的函数返回,我们就会弹出它。因此,尾调用重用当前的激活记录。举个例子,考虑一下:
当我们调用
some-function
时,我们在堆栈上为其激活记录分配空间:局部变量、参数等。然后我们调用(preprocess x)< /代码>。由于我们需要返回
some-function
并继续处理,因此我们必须保留some-function
的激活记录,因此我们为some-function
推送一个新的激活记录代码>预处理。一旦返回,我们就会弹出预处理的堆栈帧并继续。接下来,我们需要评估modified
;同样的事情也必须发生,当modified
返回时,其结果将传递给combine
。人们可能会认为我们需要创建一个新的激活记录,运行combine
,然后将其返回到some-function
,但是some-function
> 不需要对该结果执行任何操作,只需返回它即可!因此,我们覆盖当前的激活记录,但保留返回地址;当combine
返回时,它会将其值返回到等待它的值。这里,(combine (modified x) y)
是一个尾部调用,评估它不需要额外的激活记录。这就是您在Scheme中实现循环的方式,例如:
如果没有尾部调用优化,这将是低效的,并且可能会因长时间运行的循环构建大量对
my-while
的调用而溢出。然而,由于尾部调用优化,对 my-while cond body 的递归调用是一个跳转,并且不分配内存,使其与迭代一样高效。其次,这里不需要任何宏。虽然您可以抽象出
display
块,但您可以使用普通函数来完成此操作。宏允许您在某种程度上更改语言的语法 - 添加您自己的定义,实现一些不评估其所有分支的类型大小写构造,等等。当然,仍然是 s 表达式,但语义不再是简单的“计算参数并调用函数”。然而,在这里,函数调用语义就足够了。话虽如此,我认为这就是我实现代码的方式:
首先,至少在我的
mzscheme
版本中,我需要一个(require (lib "string.ss"))
行导入real->decimal-string
。接下来,我抽象出了您正在谈论的display
块。我们看到的是,每一行都想在第40列以相同的格式打印钱,在其前面打印一个标签名称和一行破折号。因此,我编写了print-report
。第一个参数是初始宽度;在本例中为40
。其余参数是字段值对。从宽度中减去每个字段的长度(加上冒号和空格的两个),我们生成一个由那么多破折号组成的字符串。我们使用format
按正确的顺序排列字段,并使用display
打印字符串。该函数对所有对进行递归(使用尾递归,因此我们不会破坏堆栈)。在主函数中,我将
(display "Income: ")
移至let
; 之前。你忽略了它的结果,那为什么要把它赋给一个变量呢?然后,我增强了if
条件来测试input
是否为 false,当string->number
无法解析输入时会发生这种情况。最后,我删除了你的局部变量,因为你所做的就是打印它们,并使用Scheme的分数语法而不是除法。 (当然,我使用print-report
而不是display
和format
。)我想就这些了;如果您对我所做的事情还有其他疑问,请随时询问。
First of all, you don't need a continuation. According to the standard, Scheme will always perform tail call optimization. A tail call is a function call which is in the final position in a function; after that call is run, nothing else will happen. In that situation, we don't need to preserve the activation record we're currently in; as soon as the function we call returns, we'll just pop it. Consequently, a tail call reuses the current activation record. As an example, consider this:
When we call
some-function
, we allocate space for its activation record on the stack: local variables, parameters, etc. We then call(preprocess x)
. Since we need to return tosome-function
and keep processing, we have to preservesome-function
's activation record, and so we push a new activation record on forpreprocess
. Once that returns, we poppreprocess
's stack frame and keep going. Next, we need to evaluatemodified
; the same thing has to happen, and whenmodified
returns, its result is passed tocombine
. One would think we'd need to create a new activation record, runcombine
, and then return this tosome-function
—butsome-function
doesn't need to do anything with that result but return it! Thus, we overwrite the current activation record, but leave the return address alone; whencombine
returns, then, it will return its value to exactly what was waiting for it. Here,(combine (modified x) y)
is a tail call, and evaluating it doesn't require an extra activation record.This is how you can implement loops in Scheme, for instance:
Without tail call optimization, this would be inefficient, and would potentially overflow with a long-running loop building up lots of calls to
my-while
. However, thanks to tail call optimization, the recursive call tomy-while cond body
is a jump, and allocates no memory, making this as efficient as iteration.Secondly, you don't need any macros here. While you can abstract out the
display
block, you can do this with a plain function. Macros allow you, on some level, to change the syntax of the language—add your own sort ofdefine
, implement some type-case construct which doesn't evaluate all its branches, etc. Of course, it's all still s-expressions, but the semantics are no longer simply "evaluate the arguments and call the function". Here, however, function-call semantics are all you need.With that said, I think this is how I'd implement your code:
First, at least in my version of
mzscheme
, I needed a(require (lib "string.ss"))
line to importreal->decimal-string
. Next, I abstracted out thedisplay
block you were talking about. What we see is that each line wants to print the money in the same format at the 40th column, printing a tag name and a row of dashes in front of it. Consequently, I wroteprint-report
. The first argument is the initial width; in this case,40
. The remaining arguments are field-value pairs. The length of each field (plus two for the colon and the space) are subtracted from the width, and we generate a string consisting of that many dashes. We useformat
to lay the fields out in the right order, anddisplay
to print the string. The function recurses over all the pairs (using tail recursion, so we won't blow the stack).In the main function, I moved the
(display "Income: ")
to before thelet
; you ignore its result, so why assign it to a variable? I then augmented theif
condition to test ifinput
is false, which happens whenstring->number
can't parse the input. Finally, I removed your local variables, since all you do is print them, and used Scheme's fraction syntax instead of division. (And of course, I useprint-report
instead ofdisplay
s andformat
s.)I think that's all; if you have any other questions about what I did, feel free to ask.