Schemeでループを行う標準的な方法は、末尾再帰を使用することです。実際、次のループがあるとしましょう。
(do ((a 0 b)
(b 1 (+ a b))
(i 0 (+ i 1)))
((>= i 10) a)
(eprintf "(fib ~a) = ~a~%" i a))
これは実際には次のようなものにマクロ展開されます。
(let loop ((a 0)
(b 1)
(i 0))
(cond ((>= i 10) a)
(else (eprintf "(fib ~a) = ~a~%" i a)
(loop b (+ a b) (+ i 1)))))
さらに、これにマクロ展開されます(これはcond
私のポイントとは無関係なので、マクロ展開はしません):
(letrec ((loop (lambda (a b i)
(cond ((>= i 10) a)
(else (eprintf "(fib ~a) = ~a~%" i a)
(loop b (+ a b) (+ i 1)))))))
(loop 0 1 0))
ここを見てletrec
、「ああ!再帰が見える!」と考えているはずです。実際にそうします(特にこの場合、末尾再帰ですletrec
が、非末尾再帰にも使用できます)。
Schemeの反復ループは、そのように書き直すことができます(名前付きlet
バージョンは、Schemeでループが慣用的に記述される方法ですが、割り当てで名前付きを使用できない場合はlet
、さらに1ステップ展開して、を使用しますletrec
)。上で説明したマクロ拡張は単純で機械的であり、一方が他方にどのように変換されるかを確認できるはずです。
あなたの質問は可変個引数関数についてどうですかと尋ねたので、次のように可変個引数関数を書くことができます:
(define (sum x . xs)
(if (null? xs) x
(apply sum (+ x (car xs)) (cdr xs))))
(これは、ところで、sum
関数を書くためのひどく非効率的な方法です。私はapply
、任意の数の引数を送信(使用)および受信(不適切なラムダリストを使用)する方法を示すために使用しています。)
アップデート
さて、ここにいくつかの一般的なアドバイスがあります:あなたは2つのループが必要になります:
- 範囲レベルを通過する外側のループ(それはあなたの可変個引数のものです)
- 各範囲レベルの数値をループする内部ループ
これらの各ループでは、次のことについて慎重に検討してください。
- 開始条件は何ですか
- 終了条件は何ですか
- 各反復で何をしたいのか
- 反復間で維持する必要のある状態があるかどうか
特に、最後のポイントについて慎重に検討してください。これは、任意の数のネストレベルが与えられた場合に、ループをネストする方法です。(以下の私のサンプルソリューションでは、それがcur
変数です。)
これらすべてを決定したら、ソリューションの一般的な構造を組み立てることができます。私のソリューションの基本構造を以下に投稿しますが、私のコードを見る前に、問題をどのように解決したいかをよく考えておく必要があります。これにより、間にどのような違いがあるかをよく理解できます。あなたのソリューションアプローチと私のもの、そしてそれはあなたが私のコードをよりよく理解するのを助けるでしょう。
do
また、最初に命令型ループ(のように)を使用して記述し、次にすべてが機能しているときに名前が付けられた同等のループに変換することを恐れないでくださいlet
。最初のセクションを読み直して、その変換を行う方法を確認してください。
そうは言っても、これが私の解決策です(詳細は削除されています):
(define (n-loop proc . ranges)
(let outer ((cur ???)
(ranges ranges))
(cond ((null? ranges) ???)
(else (do ((i (caar ranges) (+ i 1)))
((>= i (cadar ranges)))
(outer ??? ???))))))
これが機能するようになったら、do
ループをnamedに基づくループに変換する必要があることを忘れないでくださいlet
。(または、さらに進んで、外側と内側の両方のループをそれらのletrec
形式に変換する必要がある場合があります。)