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

发布于 2024-09-14 12:01:39 字数 1032 浏览 10 评论 0原文

我听说它被称为 流< /a>, 作为一个无限列表有时甚至作为惰性序列 .

以下模式的正确术语是什么? (所示为 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)

I've heard it referred to as a stream, as an infinite list, and sometimes even as a lazy sequence.

What is the correct term for the following pattern? (Clojure code shown)

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

疾风者 2024-09-21 12:01:39

简短回答:stream-builder 返回一个返回无限序列/列表的函数,必须“惰性”地对其求值(因为您无法在有限时间内求出无限长的值)。在 Clojure 世界中,您可能不应该将示例中的任何内容称为“流”,以避免与另一个概念混淆。

更长的答案:

编程语言中思想多样性的一个不幸的副作用是我们经常使用相同的单词来表示不同的含义。您提到的所有三个词(“流”、“无限列表”、“惰性序列”)均指以串行方式处理元素,在 Clojure 中我们将这些称为“序列”。但是,每个词隐含的细微差别略有不同

。 “流”通常指的是一些元素序列,并且现在经常在有限字符序列的上下文中使用,这些字符序列通常来自文件、网络源或 Unix 管道(

如果序列是以它的方式定义的) 。具有无限多个元素,我们可以将其称为无限序列。通常无限序列在内部表示为 链表。 ,所以我们可以称这些为“无限列表”,不过,说实话,我更喜欢在 Clojure 社区中听到“无限序列”这个术语,这样我们就不会受限于特定的实现

。 Clojure 中的“惰性序列”是指“按需”对数据结构进行顺序求值的模式。换句话说,这里强调的是评估的惰性本质;序列中特定元素的值在您要求之前不会被实际计算。

总之,在 Clojure 中,您应该使用以下单词:

  • “列表”来指代具有链表实现的事物
  • “惰性”指代按需求值的事物
  • “无限”指代非有限大小的序列(并且因此必须是惰性的)
  • “流”来引用来自外部源的类似管道(字符)序列

Short answer: stream-builder returns a function that returns an infinite sequence/list, which must be evaluated 'lazily' (since you can't evaluate something infinitely long in finite time). In the Clojure world, you should probably call none of the things in your example "streams" to avoid confusion with another concept.

Longer answer:

An unfortunate side effect of the diversity of thought in programming languages is that we often use the same words for different meanings. All three words you mentioned ("Stream", infinite list", "lazy sequence") refer to processing elements in a serial fashion, and in Clojure we call these "Sequences". However, the nuances implied by each are slightly different.

A "stream" refers generally to some sequence of elements, and is nowadays often used in the context of finite character sequences. Often these character sequences come from a file, network source, or Unix pipe.

If a sequence is defined in a way that it has an infinite number of elements, we can call it an infinite sequence. Usually infinite sequences are represented internally as a linked list , so we may call these "infinite lists". Although, to be honest I would prefer to hear the term "infinite sequence" in the Clojure community so we are not tied to a particular implementation.

Finally, the nuance of "lazy sequence" in Clojure refers to a pattern of sequential evaluation on a data structure that occurs "on demand". In other words, the emphasis here is on the lazy nature of evaluation; the value of a particular element in the sequence is not actually computed until you ask for it.

In summary, in Clojure you should use the words:

  • "list" to refer to something with a linked list implementation
  • "lazy" to refer to things that evaluate on demand
  • "infinite" to refer to sequences that aren't of finite size (and must therefore be lazy)
  • "stream" to refer to a pipe-like (character) sequence from an external source
来日方长 2024-09-21 12:01:39

这不是您问题的答案,但为了编写“漂亮”的 clojure 代码,我想用您的示例指出一些事情。

函数式风格的好处之一是能够将函数组合在一起。但是,如果您看一下您编写的函数,就会发现它们在不依赖其他地方提供的功能的情况下单独做的事情并不多。

例如,stream-builder 仅返回一个 n 的两元素列表和一个处理伪递归的匿名函数。

我的猜测是,您试图采用惰性序列,但这样做需要支持函数来了解实现下一个元素的实现细节,即 streamsecond $。值得庆幸的是,您可以按如下方式完成它:

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

上面的代码将懒惰 返回 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)

This is not an answer to your question, but in the interest of writing "nice" clojure code, I wanted to point out a few things with your example.

One of the benefits of the functional style is the ability to compose functions together. But if you take a look at the functions you've written, they individually don't do much without depending on functionality provided elsewhere.

For example, stream-builder just returns a two-element list of n and an anonymous function to handle pseudo-recursion.

My guess is that you were trying to go for a lazy sequence, but doing it that way requires support functions that are aware of the implementation details to realize the next element, namely, stream and second$. Thankfully you can instead accomplish it as follows:

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

The above will actually return an infinite sequence of values, so we need to pull out the limiting behavior that's bundled inside 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)))))))

The above will lazily return up to n elements of coll:

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

In the end, each function does one thing well, and is composable with other functions:

Finally, you defined odd to be the lazy sequence of odd integers. The danger is that sequence is going to stick around as it gets realized (this is known as "holding onto the head" of a sequence). This can unnecessarily take up excess memory to hold onto every element ever realized, and prevents garbage collection.

To solve this, either do not def the lazy sequence, or def it with the limit, e.g.:

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

For future reference, what we have just written is available in the core lib as iterate and take, respectively:

user=> (take 5 (iterate inc 1))
(1 2 3 4 5)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文