Ваши примеры будут работать быстрее, потому что, если вы используете recur
, вы уже сообщаете компилятору, что у вас есть хвостовая рекурсивная функция, и это позволяет компилятору генерировать байт-код, который использует goto
(как обычный императивный цикл)
Конечно, есть некоторые преимущества, если JVM оптимизирует хвостовой вызов.
Вам больше не придется использовать recur (если вы не хотите), чтобы вы могли написать такую функцию (хвостовая рекурсивная функция)
(defn testfn [n] (when (not= 1000 n) (testfn n)))
В настоящее время JVM не может обнаружить рекурсию хвоста. С добавлением оптимизации хвостового вызова JVM смогла увидеть вышеописанную функцию так же, как если бы вы написали это (и, следовательно, получили императивную скорость цикла):
(defn testfn [n] (when (not= 1000 n) (recur n)))
Так что не так уж и много улучшений, но есть еще один случай, когда оптимизация хвостового вызова действительно хороша.
Если у вас есть функции, которые вызывают друг друга (иногда даже больше, чем две), и вам не нужно держать в стеке (являются хвостовыми рекурсивными), JVM может оптимизировать их. Сейчас это невозможно, потому что вы не можете сказать recur
перейти к какой-либо другой функции. Вот пример.
(defn even [n] (if (zero? n) true (odd? (dec n)))
(defn odd [n] (if (zero? n) false (even (dec n)))
Если вы попробуете это с большим числом, знайте, что вы перевернете стек, но с оптимизацией хвостового вызова вы не будете.
У меня осталось небольшое дополнение. Есть функция под названием trampoline
, которая позволяет вам уже делать это (с небольшим изменением стиля программирования и некоторыми накладными расходами) Вместо объяснения trampoline
я отошлю вас к блогу, который делает именно это:
http://pramode.net/clojure/2010/05/08/clojure-trampoline/