实用方案编程

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.)

我认为这就是全部; 如果您对我所做的事有任何其他疑问,请随时提出.

  • 这是一个很好的答案.非常感谢你 - 我得到了很好的理论和实践解释; 确切地(如果不是更多)比我想要的那样.非常感谢你! (2认同)