OK:
これはロングテール再帰と私の実装です。再帰的な手順もまた、再帰的プロセスを展開します。わからないなぜあなたは定義がどのように異なっているかを見る以外にこれが欲しい。 末尾再帰モジュロコンロー(hereおよびon wikipedia)も読んでください。この質問に関連しています。
;; recursive procedure, recursive process
(define (build-list n f)
(define (aux m)
(if (equal? m n)
empty
(cons (f m) (aux (add1 m)))))
(aux 0))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
aux
呼び出しが末尾位置にもはやどのように注意してください - それは、その引数にaux
コールを評価されるまで、すなわち、cons
が評価を完了することはできません。このプロセスは、スタックに進化し、次のようになります。
(cons (f 0) ...)
(cons (f 0) (cons (f 1) ...))
(cons (f 0) (cons (f 1) (cons (f 2) ...)))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) ...))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) (cons (f 4) ...)))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) (cons (f 4) empty)))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3) (cons (f 4)'())))))
(cons (f 0) (cons (f 1) (cons (f 2) (cons (f 3)'(16)))))
(cons (f 0) (cons (f 1) (cons (f 2)'(9 16))))
(cons (f 0) (cons (f 1)'(4 9 16)))
(cons (f 0)'(1 4 9 16))
'(0 1 4 9 16)
あなたはcons
コールが記入されており、最後の...
がするまでempty
で満たされていない...
まで開いてぶら下がって残っていることがわかります。 m
はn
に等しい。
あなたは、内側aux
手続きが気に入らない場合は、デフォルトのパラメータを使用することができますが、これは、パブリックAPIにプライベートAPIの一部を漏らすん。たぶんそれはあなたにとって有用であるかもしれませんし、多分あなたは本当に気にしないかもしれません。
;; recursive procedure, recursive process
(define (build-list n f (m 0))
(if (equal? m n)
'()
(cons (f m) (build-list n f (add1 m)))))
;; still only apply build-list with 2 arguments
(build-list 5 (lambda (x) (* x x)))
;; => '(0 1 4 9 16)
;; if a user wanted, they could start `m` at a different initial value
;; this is what i mean by "leaked" private API
(build-list 5 (lambda (x) (* x x) 3)
;; => '(9 16)
スタックセーフな実装
あなたは、特に、特に、それは書くことがいかに簡単かを考えると、芋、再帰的プロセス(スタックを成長1)が奇妙であるたいと思う理由stack-safe build-list
スタックを拡張しないプロシージャ。ここでは、線形反復プロセスを使用した再帰的プロシージャをいくつか示します。
最初は非常に単純ですが、acc
パラメータを使用してプライベートAPIを少し漏らしています。最初の解決策と同じように、aux
手順を使用して簡単に修正できます。
;; recursive procedure, iterative process
(define (build-list n f (acc empty))
(if (equal? 0 n)
acc
(build-list (sub1 n) f (cons (f (sub1 n)) acc))))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
リスト全体が構築されるまで、それは常に1つのスタックフレームを再利用することができますので、これはめちゃくちゃ良いです
(cons (f 4) empty)
(cons (f 3) '(16))
(cons (f 2) '(9 16))
(cons (f 1) '(4 9 16))
(cons (f 0) '(1 4 9 16))
;; => '(0 1 4 9 16)
進化のプロセスをチェックしてください。追加の利点として、0からn
までのカウンタを保持する必要はありません。代わりに、リストを後方に構築し、n-1
から0
まで数えます。
最後に、線形反復プロセスを進化させる別の再帰的な手順を示します。これは、指定されたletと継続の通過スタイルを使用します。このループは、今度はAPIの漏れを防ぐのに役立ちます。
;; recursive procedure, iterative process
(define (build-list n f)
(let loop ((m 0) (k identity))
(if (equal? n m)
(k empty)
(loop (add1 m) (λ (rest) (k (cons (f m) rest)))))))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
あなたはcompose
とcurry
を使用する場合、それは少しカントーをクリーンアップ:
;; recursive procedure, iterative process
(define (build-list n f)
(let loop ((m 0) (k identity))
(if (equal? n m)
(k empty)
(loop (add1 m) (compose k (curry cons (f m)))))))
(build-list 5 (λ (x) (* x x)))
;; => '(0 1 4 9 16)
この手順から進化プロセスは多少異なりますが、あなたは、それはまた成長しないことに気づくでしょう代わりに、ヒープ上にネストされたラムダのシーケンスを作成します。だから、これはn
の十分に大きな値のために十分であろう:
(loop 0 identity) ; k0
(loop 1 (λ (x) (k0 (cons (f 0) x))) ; k1
(loop 2 (λ (x) (k1 (cons (f 1) x))) ; k2
(loop 3 (λ (x) (k2 (cons (f 2) x))) ; k3
(loop 4 (λ (x) (k3 (cons (f 3) x))) ; k4
(loop 5 (λ (x) (k4 (cons (f 4) x))) ; k5
(k5 empty)
(k4 (cons 16 empty))
(k3 (cons 9 '(16)))
(k2 (cons 4 '(9 16)))
(k1 (cons 1 '(4 9 16)))
(k0 (cons 0 '(1 4 9 16)))
(identity '(0 1 4 9 16))
'(0 1 4 9 16)
私はリスト-LEN 'まで '0'からカウントヘルパー関数を使用したい - 1 '。 – melpomene
再帰プロシージャを呼び出し、結果を元に戻すラッパー・プロシージャを作成するだけです。 – Barmar
あなたが探している場合は、[ラケットのソース](https://github.com/racket/racket/blob/62f5b2c4e4cdefa18fa36275074ff9fe376ddaf3/racket/collects/racket/private/list.rkt#L302-L305)に実装されている方法を確認してくださいその動作をエミュレートする –