まず第一に、あなたは継続を必要としません。標準によれば、Schemeは常に末尾呼び出しの最適化を実行します。末尾呼び出しは、関数の最後の位置にある関数呼び出しです。その呼び出しが実行された後、他には何も起こりません。そのような状況では、現在のアクティベーションレコードを保持する必要はありません。呼び出す関数が返されるとすぐに、それをポップします。したがって、末尾呼び出しは現在のアクティブ化レコードを再利用します。例として、これを考えてみましょう。
(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
できますが、単純な関数を使用してこれを行うことができます。マクロを使用すると、あるレベルで、言語の構文を変更できます。独自の種類を追加したり、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))))))
まず、少なくとも私のバージョンでは、インポートmzscheme
する行が必要でした。次に、あなたが話していたブロックを抽象化しました。私たちが見ているのは、各行が40列目に同じ形式でお金を印刷し、その前にタグ名とダッシュの行を印刷したいということです。その結果、私は書いた。最初の引数は初期幅です。この場合、。残りの引数はフィールドと値のペアです。各フィールドの長さ(コロンとスペースの場合は2を加えたもの)が幅から差し引かれ、その数のダッシュで構成される文字列が生成されます。フィールドを正しい順序で配置し、文字列を印刷するために使用します。関数はすべてのペアで再帰します(末尾再帰を使用するため、スタックをブローしません)。(require (lib "string.ss"))
real->decimal-string
display
print-report
40
format
display
main関数で、 ;の(display "Income: ")
前に移動しました。let
結果を無視するのに、なぜそれを変数に割り当てるのですか?次に、条件を拡張して、falseif
かどうかをテストしました。これは、入力を解析できない場合に発生します。最後に、ローカル変数を削除しました。これは、ローカル変数を出力するだけで、除算の代わりにSchemeの分数構文を使用したためです。(そしてもちろん、私はsとsの代わりに使用します。)input
string->number
print-report
display
format
それだけだと思います。私がしたことについて他に質問があれば、遠慮なく質問してください。