Каков правильный термин для следующего шаблона функционального программирования? - PullRequest
10 голосов
/ 16 августа 2010

Я слышал, это называется поток , как бесконечный список , , а иногда даже как ленивая последовательность .

Какой правильный термин для следующего паттерна? (Показан код закрытия)

(def first$ first)

(defn second$ [str]
  (cond
    (empty? str) ()
    true ((first (rest str)))))

(defn stream-builder [next_ n]
  (cons n (cons (fn [] (stream-builder next_ (next_ n))) ())))

(defn stream [str n]
  (cond
    (= 0 n) ()
    true (cons (first$ str) (stream (second$ str) (- n 1)))))

(def odd 
  (stream-builder (fn [n] 
        (+ 2 n))1))

(println (stream odd 23))

> (1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45)

Ответы [ 2 ]

14 голосов
/ 16 августа 2010

Краткий ответ: stream-builder возвращает функцию, которая возвращает бесконечную последовательность / список, который должен оцениваться «лениво» (поскольку вы не можете вычислять что-то бесконечно длинное за конечное время). В мире Clojure вам, вероятно, не следует называть ни одну из вещей в вашем примере «потоками», чтобы избежать путаницы с другой концепцией.

Более длинный ответ:

К сожалению, побочным эффектом разнообразия мышления в языках программирования является то, что мы часто используем одни и те же слова для разных значений. Все три слова, которые вы упомянули («Поток», бесконечный список »,« ленивая последовательность »), относятся к последовательной обработке элементов, и в Clojure мы называем эти« последовательности ». Однако нюансы, подразумеваемые каждым, немного отличаются.

«Поток» обычно относится к некоторой последовательности элементов и в настоящее время часто используется в контексте конечных символьных последовательностей. Часто эти последовательности символов происходят из файла, сетевого источника или канала Unix.

Если последовательность определена так, что она имеет бесконечное число элементов, мы можем назвать ее бесконечной последовательностью. Обычно бесконечные последовательности представляются внутри как связанный список , поэтому мы можем назвать эти «бесконечные списки». Хотя, если честно, я бы предпочел услышать термин «бесконечная последовательность» в сообществе Clojure, чтобы мы не были привязаны к конкретной реализации.

Наконец, нюанс «ленивой последовательности» в Clojure относится к шаблону последовательной оценки в структуре данных, которая возникает «по требованию». Другими словами, акцент здесь делается на ленивый характер оценки; значение определенного элемента в последовательности фактически не вычисляется, пока вы не запросите его.

Таким образом, в Clojure вы должны использовать слова:

  • «список» для обозначения чего-либо с реализацией связанного списка
  • «ленивый» относится к вещам, которые оцениваются по требованию
  • "бесконечный" для обозначения последовательностей, которые не имеют конечного размера (и поэтому должны быть ленивыми)
  • «поток» для обозначения конвейерной (символьной) последовательности из внешнего источника
10 голосов
/ 17 августа 2010

Это не ответ на ваш вопрос, но в интересах написания «красивого» кода clojure я хотел бы отметить несколько вещей на вашем примере.

Одним из преимуществ функционального стиля является возможность составлять функции вместе. Но если вы посмотрите на написанные вами функции, они по отдельности мало что делают без зависимости от функциональности, предоставленной в другом месте.

Например, stream-builder просто возвращает двухэлементный список n и анонимную функцию для обработки псевдорекурсии.

Я предполагаю, что вы пытались использовать ленивую последовательность, но для этого нужно, чтобы вспомогательные функции знали о деталях реализации для реализации следующего элемента, а именно stream и second$. К счастью, вместо этого вы можете сделать это следующим образом:

(defn stream-builder [f x] ; f is idiomatic for a function arg
  (lazy-seq                ; n is idiomatic for a count, so use x instead
    (cons x 
      (stream-builder f (f x)))))

Вышеприведенное фактически возвращает бесконечную последовательность значений, поэтому нам нужно вытащить ограничивающее поведение, связанное внутри stream:

(defn limit [n coll] ; coll is idiomatic for a collection arg
  (lazy-seq          ; flipped order, fns that work on seqs usually take the seq last
    (when (pos? n)
      (when-let [s (seq coll)]
        (cons (first s) 
          (limit (dec n) (next s)))))))

Вышеуказанное лениво вернет до n элементов coll:

user=> (limit 5 (stream-builder inc 1))
(1 2 3 4 5)

В конце концов, каждая функция хорошо выполняет одну задачу, и ее можно комбинировать с другими функциями:

Наконец, вы определили odd как ленивую последовательность нечетных целых чисел. Опасность заключается в том, что последовательность будет слипаться по мере того, как она будет реализована (это известно как «удерживание головы» последовательности). Это может излишне занимать лишнюю память для хранения каждого когда-либо реализованного элемента и предотвращает сборку мусора.

Чтобы решить эту проблему, либо не определяйте ленивую последовательность, либо не определяйте ее с помощью лимита, например ::1010 *

(def odds (limit 23 (stream-builder #(+ 2 %) 1)))

Для дальнейшего использования то, что мы только что написали, доступно в базовой библиотеке как iterate и take соответственно:

user=> (take 5 (iterate inc 1))
(1 2 3 4 5)
...