为了设置一些上下文,我正在学习 Clojure,以及更普遍的 Lisp 开发。在通往 Lisp 的道路上,我目前正在研究“小”系列,以巩固函数式编程和基于递归的解决方案解决方案的基础。在“The Little Schemer”中,我完成了许多练习,但是,我在将其中一些练习转换为 Clojure 时遇到了一些困难。更具体地说,我正在努力将它们转换为使用“recur”以启用 TCO。例如,下面是“occurs*”函数(来自 Little Schemer)的基于 Clojure 的实现,它计算出现在 S 表达式列表中的原子的出现次数:
(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?如果是后者,一般来说,Clojure 程序使用需要在 S 表达式列表上递归的一般模式应该是什么?对于它的价值,我已经看到使用了多方法 w/lazy-seq 技术(Halloway 的“Programming Clojure”的第 151 页供参考)“Replace Recursion with Laziness”——但我不确定如何应用该模式在这个例子中,我不是试图建立一个列表,而是计算一个整数值。
提前感谢您对此的任何指导。