Как избежать двойного вызова функции в (recur..)

Пожалуйста, рассмотрите этот блок кода.

(loop [k from res '()]
    (if (< (count res) n)
      (recur (next-num k)  (conj res (next-num k)))
      (sort res)))

Теперь предположим, что функция (next-num k) выполняет некоторые дорогостоящие вычисления. Мы не можем позволить себе назвать это дважды. Какая будет альтернатива? Я новичок в Clojure и не знаю многих основных функций. Но я уверен, что должен быть способ.


person Adeel Ansari    schedule 18.12.2013    source источник


Ответы (2)


Используйте 1_:

(loop [k from res '()]
    (if (< (count res) n)
      (let [the-next-num (next-num k)]
          (recur the-next-num  (conj res the-next-num)))
      (sort res)))
person Nathan Davis    schedule 18.12.2013
comment
Или полностью избегайте цикла: (->> (iterate next-num from) rest (take n) sort) - person Beyamor; 18.12.2013
comment
@Beyamor: Ваше предложение работает как шарм. Но я понятия не имею, почему. Не могли бы вы уточнить, возможно, в отдельном вашем ответе. - person Adeel Ansari; 18.12.2013
comment
Я восстановил свой ответ и объяснил код без петель, но @Nathan определенно опередил меня при использовании let. - person Beyamor; 18.12.2013

Как сказал @NathanDavis, let позволяет указывать промежуточные значения:

(loop [k from res '()]
  (if (< (count res) n)
    (let [next-k (next-num k)]
      (recur next-k (conj res next-k)))
    (sort res)))

Однако, прежде чем перейти к loop, стоит посмотреть, сможете ли вы скомпоновать основные функции с тем же эффектом. Часто вы можете написать что-то менее сложное и раскрыть важные детали.

Основная часть кода касается построения последовательности повторяющихся применений next-num. К счастью, для этого есть основная функция: iterate. Используя iterate, мы можем создать бесконечную ленивую последовательность значений:

(iterate next-num from)
; => (from, from', from'', ...)

Однако нам не нужно первое из этих значений. Мы можем получить остальную часть последовательности с помощью rest:

(rest
  (iterate next-num from))
; => (from', from'', from''', ...)

На данный момент мы можем получить n значений с помощью take:

(take n
  (rest
    (iterate next-num from)))
; => (from', from'', from''')

И, наконец, мы можем отсортировать эти значения n:

(sort
  (take n
    (rest
      (iterate next-num from))))
 ; => (from'', from', from''')

Конечно, глубоко вложенные вызовы функций вскоре становятся неудобными. Макрос потоковой обработки ->> (как и его брат ->) — это немного синтаксического сахара, который позволяет нам преобразовать наш код во что-то более приятное:

(->>
  (iterate next-num from)
  rest
  (take n)
  sort)

Итак, вы видите, как мощная библиотека функций управления последовательностями позволяет нам отказаться от низкоуровневых циклов.

person Beyamor    schedule 18.12.2013
comment
Шикарное объяснение, на самом деле. Хотя я принял другой ответ, поскольку let на самом деле и точно является ответом на мой вопрос. Но такой подход определенно и однозначно лучше. - person Adeel Ansari; 18.12.2013