一千萬個為什麽

搜索

自從我觸及Scheme並決定使用Scheme實現命令行收入分配器已經有幾個月了。

我的初始實現使用了對延續的普通遞歸,但我認為延續更適合這種類型的程序。我很感激,如果有人(比我更熟練的方案)可以看看這個並提出改進建議。我是多個(顯示... 行也是使用宏的理想機會(我還沒有得到宏)。

(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的任何內容(根據我的理解),在 current-continuation返回(ab-income) </代碼>。我的第一個實現(正如我之前所說)使用了簡單的遞歸。這一切都不錯,但是如果值低於600則繼續擴展函數,我想到每次回復(ab-income)

(如果這種擔心不正確,請糾正我!)

最佳答案

首先,你不需要繼續。根據標準,Scheme將始終執行尾部呼叫優化。尾調用是函數調用,它位於函數的最終位置;在該呼叫運行之後,不會發生任何其他事情。在這種情況下,我們不需要保留我們目前所處的激活記錄;一旦我們調用的函數返回,我們就會彈出它。因此,尾調用重用當前激活記錄。舉個例子,考慮一下:

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

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 to some-function and keep processing, we have to preserve some-function's activation record, and so we push a new activation record on for preprocess. Once that returns, we pop preprocess's stack frame and keep going. Next, we need to evaluate modified; the same thing has to happen, and when modified returns, its result is passed to combine. One would think we'd need to create a new activation record, run combine, and then return this to some-function—but some-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; when combine 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.

這是你在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 的遞歸調用是一個跳轉,並且不分配內存,這使得它與叠代一樣高效。

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 of define, 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.

話雖如此,我認為這是我實現你的代碼的方式:

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

First, at least in my version of mzscheme, I needed a (require (lib "string.ss")) line to import real->decimal-string. Next, I abstracted out the display 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 wrote print-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 use format to lay the fields out in the right order, and display 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 the let; you ignore its result, so why assign it to a variable? I then augmented the if condition to test if input is false, which happens when string->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 use print-report instead of displays and formats.)

我認為這就是全部;如果您對我所做的事有任何其他疑問,請隨時提出。

轉載註明原文: 實用方案編程

猜你喜歡