代码之家  ›  专栏  ›  技术社区  ›  Ixmatus

实用方案编制

  •  5
  • Ixmatus  · 技术社区  · 16 年前

    我已经有几个月没有接触过这个计划,并决定实施一个命令行的收入分配使用计划。

    我的初始实现在延续上使用了简单的递归,但是我认为延续更适合这种类型的程序。如果有人(比我更精通这个计划)能看看这个并提出改进建议,我会很感激的。我就是那个混血儿 (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收入) current-continuation . 我的第一个实现(如我前面所说)使用了纯简递归。一点也不坏,但我想每次回电 (AB收入) 如果值低于600,则继续扩展函数。

    (如果这种担心是错误的,请纠正我!)

    1 回复  |  直到 16 年前
        1
  •  17
  •   Antal Spector-Zabusky    16 年前

    首先,你不需要继续。根据标准,方案将始终执行 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和 格式 美国)

    我想就这些了;如果你对我所做的有任何其他问题,可以问。

    推荐文章