Чтобы задать некоторый контекст, я нахожусь в процессе изучения Clojure и разработки Lisp в целом. На моем пути к Lisp я в настоящее время работаю над серией "Little", пытаясь укрепить фундамент в функциональном программировании и решении рекурсивных решений. В «Маленьком программисте» я проработал многие упражнения, однако мне немного сложно преобразовать некоторые из них в Clojure. В частности, я изо всех сил пытаюсь преобразовать их для использования «повторения», чтобы включить TCO. Например, вот реализация на основе Clojure функции «происходит *» (от Little Schemer), которая подсчитывает количество появлений атома, появляющегося в списке 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)))))
В основном, (происходит 'abc' (abc (def abc) (abc (abc def) (def (((((abc))))))))
будет оцениваться как 5. Очевидная проблема состоит в том, что это определение использует стековые кадры и взорвет стек, если задан слишком глубокий список S-выражений.
Теперь я понимаю возможность рефакторинга рекурсивных функций, чтобы использовать параметр аккумулятора, чтобы разрешить размещение рекурсивного вызова в хвостовой позиции (чтобы учесть совокупную стоимость владения), но я борюсь, применим ли этот параметр к таким ситуациям, как Вот этот.
Вот как далеко я смогу продвинуться, если попытаюсь реорганизовать это с помощью «повторения» вместе с использованием параметра аккумулятора:
(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 из-за отсутствия совокупной стоимости владения? Если последнее, вообще говоря, каким должен быть общий шаблон для программ Clojure, которые должны повторяться по списку S-выражений? Как бы то ни было, я видел, как использовался метод multi method w / lazy-seq (стр. 151 книги Halloway "Programming Clojure" для справки) для "Replace Recursion with Laziness" - но я не уверен, как применить этот шаблон. в этом примере, в котором я не пытаюсь создать список, а вычисляю одно целое значение.
Заранее благодарим вас за любые указания по этому поводу.