Является ли рекурсия запахом (в идиоматическом Clojure) из-за застежек-молний и HOF?
Классическая книга "Маленький лиспер" ( The Little Schemer) основана на двух больших идеях.
- Вы можете решить большинство проблем рекурсивным способом (вместо использования циклов) (при условии, что у вас есть Tail Call Optimization)
- Lisp великолепен, потому что его легко внедрить в себя.
Теперь можно подумать, что это верно для всех языков Lispy (включая Clojure). Проблема в том, что книга является артефактом своего времени (1989 г.), вероятно, до того, как функциональное программирование с функциями высшего порядка (HOF) было тем, что мы имеем сегодня (или, по крайней мере, считалось приемлемым для студентов).
Преимущество рекурсии (по крайней мере, частично) заключается в простоте обхода вложенных структур данных, таких как ('a 'b ('c ('d 'e)))
,
(def leftmost
(fn [l]
(println "(leftmost " l)
(println (non-atom? l))
(cond
(null? l) '()
(non-atom? (first l)) (leftmost (first l))
true (first l))))
Теперь с функциональными застежками-молниями - у нас есть нерекурсивный подход к обходу вложенных структур данных, и мы можем обходить их, как и любые ленивые структуры данных. Например:
(defn map-zipper [m]
(zip/zipper
(fn [x] (or (map? x) (map? (nth x 1))))
(fn [x] (seq (if (map? x) x (nth x 1))))
(fn [x children]
(if (map? x)
(into {} children)
(assoc x 1 (into {} children))))
m))
(def m {:a 3 :b {:x true :y false} :c 4})
(-> (map-zipper m) zip/down zip/right zip/node)
;;=> [:b {:y false, :x true}]
Теперь кажется, что вы можете решить любую проблему обхода вложенного списка одним из следующих способов:
-
zipper
как указано выше, или -
zipper
который обходит структуру и возвращает набор ключей, которые позволят вам изменить структуру, используяassoc
,
Предположения:
- Я предполагаю, конечно, структуры данных, которые фиксированного размера, и полностью известны до прохождения
- Я исключаю сценарий потокового источника данных.
Мой вопрос: является ли рекурсия запахом (в идиоматическом Clojure) из-за застежек-молний и HOF?
2 ответа
Я бы сказал, что да, если вы делаете ручную рекурсию, вы должны хотя бы пересмотреть, нужно ли вам это делать. Но я бы не сказал, что молнии имеют к этому отношение. Мой опыт работы с застежками-молниями заключается в том, что они имеют теоретическое применение и очень интересны для новичков Clojure, но имеют небольшую практическую ценность, когда вы овладеваете вещами, потому что ситуации, в которых они полезны, исчезающе редки.
На самом деле из-за функций высшего порядка, которые уже реализовали общие рекурсивные шаблоны, ручная рекурсия встречается редко. Тем не менее, это не тот случай, когда вам никогда не следует использовать ручную рекурсию: это всего лишь предупреждающий знак, указывающий на то, что вы можете сделать что-то еще. Я даже не могу вспомнить ситуацию за четыре года использования Clojure, когда мне действительно нужна молния, но в итоге я довольно часто использую рекурсию.
Идиомы Clojure препятствуют явной рекурсии, потому что стек вызовов ограничен: обычно до 10K глубиной. Внесение поправок в первое из шести правил функционального программирования Clojure компании Halloway & Bedra ( Программирование Clojure (стр. 89)),
Избегайте неограниченной рекурсии. JVM не может оптимизировать рекурсивные вызовы, и программы Clojure, выполняющие рекурсивные операции без ограничений, будут разрушать их стек.
Есть пара паллиативов:
recur
имеет дело с хвостовой рекурсией.- Ленивые последовательности могут превратить глубокий стек вызовов в неглубокий стек вызовов
через разворачивающуюся структуру данных. Много HOF в последовательности
библиотека, такая какmap
а такжеfilter
, сделай это.