Рекурсия по списку s-выражений в Clojure

Чтобы задать некоторый контекст, я нахожусь в процессе изучения 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" - но я не уверен, как применить этот шаблон. в этом примере, в котором я не пытаюсь создать список, а вычисляю одно целое значение.

Заранее благодарим вас за любые указания по этому поводу.

13
задан Paul Evans 8 November 2011 в 04:09
поделиться