scheme - 实用方案编程

标签 scheme racket continuations

自从我接触到 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-income)要求输入,如果提供低于 600 的任何值(据我的理解)返回 (ab-income)current-continuation .我的第一个实现(正如我之前所说的)使用纯简递归。也不错,但我认为每次回电到 (ab-income)如果该值低于 600,则继续扩展该功能。

(如果这种担心不正确,请纠正我!)

最佳答案

首先,你不需要继续。按照标准,Scheme会一直执行tail call optimization .尾调用是一个函数调用,它位于函数的最后位置;运行该调用后,不会发生任何其他事情。在这种情况下,我们不需要保留我们当前所在的激活记录;一旦我们调用的函数返回,我们就会弹出它。因此,尾调用会重用当前的激活记录。例如,考虑一下:

(define (some-function x y)
  (preprocess x)
  (combine (modified x) y))
(some-function alpha beta)

当我们调用 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)))))

如果没有尾调用优化,这将是低效的,并且可能会因长时间运行的循环而溢出,从而构建大量对 my-while 的调用。 .然而,由于尾调用优化,对 my-while cond body 的递归调用是一个跳转,并且不分配内存,使其与迭代一样高效。

其次,这里不需要任何宏。虽然您可以抽象出 display block ,你可以用一个普通的函数来做到这一点。宏允许你在某种程度上改变语言的语法——添加你自己的 define ,实现一些不评估其所有分支的类型案例构造,等等。当然,它仍然是 s-expression,但语义不再简单地“评估参数并调用函数”。然而,这里只需要函数调用语义。

话虽如此,我认为这就是我实现代码的方式:
(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 .接下来,我抽象出display block 你说的。我们看到的是,每一行都想在第 40 列以相同的格式打印钱,打印一个标签名称和前面的一行破折号。因此,我写了 print-report .第一个参数是初始宽度;在这种情况下,40 .其余参数是字段值对。每个字段的长度(冒号和空格加上两个)从宽度中减去,我们生成一个由那么多破折号组成的字符串。我们使用 format以正确的顺序排列字段,display打印字符串。该函数对所有对进行递归(使用尾递归,因此我们不会破坏堆栈)。

在主函数中,我移动了 (display "Income: ")let 之前;你忽略了它的结果,那么为什么将它分配给一个变量呢?然后我增加了 if测试条件 input为假,发生在 string->number无法解析输入。最后,我删除了你的局部变量,因为你所做的只是打印它们,并使用 Scheme 的分数语法而不是除法。 (当然,我使用 print-report 而不是 displayformat 。)

我认为仅此而已;如果您对我所做的事情有任何其他问题,请随时提问。

关于scheme - 实用方案编程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2659227/

相关文章:

lisp - 可以订车!和设置CDR!被实现为宏?

garbage-collection - 在 Scheme/Lisp 实现中不使用垃圾收集器

scheme - DrRacket 自定义按键绑定(bind)

scheme - 如何减少Scheme中的值?

scheme - 在 do 循环中填充列表返回列表为空

racket - 将强制转换应用于类型化 Racket 中的动态所需功能

haskell - 如何在Haskell中解释callCC?

scheme - 递归函数计算从1到n的所有数字的总和?

c++ - 使用 boost::future with continuations 和 boost::when_all

scala - Kiselyov zipper 的惯用 Scala 翻译?