いくつかのコンテキストを設定するために、私はClojureとLisp開発をより一般的に学ぶ過程にあります。Lispへの道のりで、私は現在、関数型プログラミングと再帰ベースのソリューション解決の基盤を固めるために「Little」シリーズに取り組んでいます。「TheLittleSchemer」では、多くの演習を行ってきましたが、それらの一部をClojureに変換するのに少し苦労しています。具体的には、TCOを有効にするために「recur」を使用するように変換するのに苦労しています。たとえば、S式のリスト内に現れるアトムの出現回数をカウントする「occurs *」関数(LittleSchemerから)のClojureベースの実装を次に示します。
(defn atom? [l]
(not (list? l)))
(defn occurs [a lst]
(cond
(empty? lst) 0
(atom? (first lst))
(cond
(= a (first lst)) (inc (occurs a (rest lst)))
true (occurs a (rest lst)))
true (+ (occurs a (first lst))
(occurs a (rest lst)))))
基本的に、(occurs 'abc '(abc (def abc) (abc (abc def) (def (((((abc)))))))))
は5と評価されます。明らかな問題は、この定義がスタックフレームを消費し、S式のリストが深すぎるとスタックを爆破することです。
これで、再帰関数をリファクタリングしてアキュムレータパラメータを使用し、再帰呼び出しをテール位置に配置できるようにするオプションを理解しました(TCOを可能にするため)が、このオプションがこのような状況にも適用できるかどうかに苦労しています。
アキュムレータパラメータを使用して「recur」を使用してこれをリファクタリングしようとすると、次のようになります。
(defn recur-occurs [a lst]
(letfn [(myoccurs [a lst count]
(cond
(empty? lst) 0
(atom? (first lst))
(cond
(= a (first lst)) (recur a (rest lst) (inc count))
true (recur a (rest lst) count))
true (+ (recur a (first lst) count)
(recur a (rest lst) count))))]
(myoccurs a lst 0)))
ですから、もうすぐそこにいるような気がしますが、完全ではありません。明らかな問題は、リストの先頭がアトムではない私の「else」句です。概念的には、リストの最初の要素を繰り返した結果と、リストの残りの要素を繰り返した結果を合計したいと思います。繰り返しをテール位置に移動できるように、これをリファクタリングする方法について頭の中で苦労しています。
再帰呼び出しをここで適用する必要があるテール位置に配置するための「アキュムレータ」パターンに追加のテクニックはありますか、それとも単に問題がより「基本的」であり、Clojureベースのクリーンなソリューションがないということです。 JVMにはTCOがないためですか?後者の場合、一般的に言えば、S式のリストを繰り返す必要があるClojureプログラムが使用する一般的なパターンは何でしょうか?価値のあることとして、「再帰を怠惰に置き換える」に使用されるマルチメソッドw / lazy-seq手法(Hallowayの「プログラミングClojure」の151ページを参照)を見てきましたが、そのパターンを適用する方法がわかりません。この例では、リストを作成するのではなく、単一の整数値を計算しようとしています。
よろしくお願いします。