以下函数式编程模式的正确术语是什么?

10

我听说过它被称为,也被称为无限列表,有时甚至被称为惰性序列.

以下模式的正确术语是什么?(Clojure代码如下)

(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)

1
我不确定你在问什么。看起来你已经找到了三个名称。在你的想法中,有什么能让任何一个答案比这些同义词更“正确”吗? - Ken
2个回答

14
简短回答:`stream-builder` 返回一个返回无限序列/列表的函数,必须“懒惰”地评估它(因为你不能在有限时间内计算出无限长的东西)。在Clojure世界中,您应该避免将示例中的任何内容称为“流”,以避免与另一个概念混淆。
更长的回答:
不幸的是,编程语言中思想的多样性会导致我们经常使用相同的词汇表示不同的含义。您提到的所有三个词(“Stream”,“infinite list”,“lazy sequence”)都指按顺序处理元素,并且在Clojure中我们称之为“Sequences”。但是,每个词所暗示的细微差别略有不同。
“Stream”通常是指某些元素序列,并且现在通常在有限字符序列的上下文中使用。通常,这些字符序列来自文件、网络源或Unix管道。
如果以一种定义了具有无限数量元素的方式,则可以将其称为无限序列。通常情况下,无限序列在内部表示为linked list,因此我们可以将其称为“infinite lists”。尽管如此,老实说,在Clojure社区中我更愿意听到术语“infinite sequence”,这样我们就不会被特定的实现所束缚。
最后,在Clojure中,“lazy sequence”的细微差别指的是在“按需”对数据结构进行顺序评估的模式。换句话说,这里强调的是评估的懒惰性质;直到您要求它,才实际计算序列中特定元素的值。
总之,在Clojure中应该使用以下词汇:
  • 使用"list"来指代使用链表实现的内容
  • 使用"lazy"来指代按需求进行计算的内容
  • 使用"infinite"来指代不具有有限大小的序列(因此必须是lazy)
  • 使用"stream"来指代来自外部源的类似管道的(字符)序列

10

这不是对你问题的回答,但为了编写“好看”的Clojure代码,我想指出你的示例中的一些问题。

函数式风格的一个好处是能够将函数组合在一起。但是如果您查看您编写的函数,它们单独做不了什么事情,而是依赖于其他地方提供的功能。

例如,stream-builder 只返回包含n和用于处理伪递归的匿名函数的两个元素的列表。

我的猜测是,您试图使用惰性序列,但以那种方式进行需要支持了解实现细节的支持函数来实现下一个元素,即 streamsecond$。幸运的是,您可以通过以下方式完成:

(defn stream-builder [f x] ; f 是函数参数的惯用法
  (lazy-seq
    (cons x 
      (stream-builder f (f x)))))

上述代码实际上将返回无限数量的值,因此我们需要提取 stream 中捆绑的限制行为:

(defn limit [n coll] ; coll 是集合参数的惯用法
  (lazy-seq          ; 翻转顺序,操作序列的函数通常会将序列作为最后一个参数
    (when (pos? n)
      (when-let [s (seq coll)]
        (cons (first s) 
          (limit (dec n) (next s)))))))

上述代码将惰性地返回 coll 的前 n 个元素:

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

最终,每个函数都可以很好地完成一件事,并且可以与其他函数组合使用:

最后,你定义了odd作为奇整数的惰性序列。危险在于当序列被实现时它会一直存在(这就是保留序列"头部"的问题)。这可能会不必要地占用额外的内存来保存每个已实现的元素,并阻止垃圾回收。

为了解决这个问题,可以选择不定义惰性序列,或使用限制值来定义,例如:

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

以后参考,我们所写的内容可以分别使用核心库中的iteratetake函数:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接