【问题标题】:What's the difference between a sequence and a collection in ClojureClojure 中的序列和集合有什么区别
【发布时间】:2013-11-19 23:18:51
【问题描述】:

我是一名 Java 程序员,并且是 Clojure 的新手。从不同的地方,我看到序列和集合在不同的情况下使用。但是,我不知道它们之间的确切区别是什么。

举几个例子:

1) 在 Clojure 的 Sequence 文档中:

The Seq interface
(first coll)
  Returns the first item in the collection. 
  Calls seq on its argument. If coll is nil, returns nil.
(rest coll)
  Returns a sequence of the items after the first. Calls seq on its argument. 
  If there are no more items, returns a logical sequence for which seq returns nil.
(cons item seq)
  Returns a new seq where item is the first element and seq is the rest.

如您所见,在描述 Seq 接口时,前两个函数(first/rest)使用coll,这似乎表明这是一个集合,而cons 函数使用seq,似乎表明这一点是一个序列。

2) 有称为coll?seq? 的函数可用于测试值是集合还是序列。显然是集合和顺序不同。

3) 在 Clojure 关于 'Collections' 的文档中说:

因为集合支持seq函数,所有的sequence 函数可以与任何集合一起使用

这是否意味着所有集合都是序列?

(coll? [1 2 3]) ; => true 
(seq? [1 2 3]) ; => false

上面的代码告诉我情况并非如此,因为[1 2 3] 是一个集合但不是一个序列。

我认为这对于 Clojure 来说是一个非常基本的问题,但我无法找到一个地方清楚地解释它们的区别是什么以及在不同情况下我应该使用哪个。任何意见表示赞赏。

【问题讨论】:

  • 最大的困惑源于seq?不测试“事物”是否是“序列”(或者更确切地说,是“序列集合”),而是测试它是否是@987654332 @(即可以被firstnextrest 操作的东西,技术上是clojure.lang.PersistentVector$ChunkedSeq)。观察:(def x [1 2]),然后:(coll? x) => true,(sequential? x) => true,(seq? x) => false,但是! (seq? (seq x)) => 是的

标签: collections clojure abstraction


【解决方案1】:

我刚读完《The Joy of Clojure》的第 5 章 - “集合类型”,有点混乱(即该书的下一个版本需要复习)。在第 5 章第 86 页,有一张我不太满意的表格:

所以这是我的看法(经过一个月的反思后回到这里,完全更新了)。

收藏

它是一个“事物”,是其他事物的集合。

这是基于函数coll?

  • coll? 函数可用于测试。
  • 相反,coll? 返回 true 的任何东西都是集合。

coll? 文档字符串说:

如果 x 实现 IPersistentCollection,则返回 true

作为集合的事物被分为三个独立的类。不同类别的事物永远不会相等。

  • 地图 使用(map? foo) 进行测试
    • 地图(行为略有不同的两个实际实现)
    • 已排序的地图。注意:(sequential? (sorted-map :a 1) ;=> 错误
  • Sets 使用(set? foo) 进行测试
    • 设置
    • 排序集。注意:(sequential? (sorted-set :a :b)) ;=> 错误
  • 顺序集合 使用(sequential? foo) 进行测试
    • 列表
    • 矢量
    • 队列
    • 序列:(sequential? (seq [1 2 3])) ;=> 真
    • 惰性序列:(sequential? (lazy-seq (seq [1 2 3]))) ;=> 真

Java 互操作的东西不在此范围内:

  • (coll? (to-array [1 2 3])) ;=> 错误
  • (map? (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))) ;=> 错误

顺序收集(“链”)

它是一个“事物”,一个集合包含其他事物根据特定的、稳定的顺序

这是基于函数sequential?

  • 函数sequential? 可用于对此进行测试。
  • 相反,sequential? 返回 true 的任何内容都是顺序集合。

sequential? 文档字符串说:

如果 coll 实现了 Sequential,则返回 true

注意:“sequential”是形容词!在“The Joy of Clojure”中,形容词被用作名词,这真的非常非常令人困惑:

"Clojure 将每个集合数据类型分为三种类型之一 逻辑类别或分区:顺序、映射和集合。”

应该使用“顺序事物”或“顺序集合”(如上所述),而不是“顺序”。另一方面,在mathematics 中已经存在以下词:“链”、“全序集”、“单序集”、“线性有序集”。 “链条”听起来很棒,但没有人使用这个词。丢人!

《Clojure 之乐》也有这样的说法:

注意基于类型的谓词!

Clojure 包含一些谓词,其名称类似于单词 just 定义。虽然它们不经常使用,但似乎值得 提到它们可能并不完全意味着这里的定义 可能会建议。例如,每个对象是哪个顺序的?返回 true 是一个顺序集合,但对于某些返回 false 也是顺序的[更好:“可以认为是顺序的 集合”]。这是因为实现细节可能是 在未来版本的 Clojure 中有所改进 [也许这已经是 完成了吗?]

序列(也称为“序列抽象”)

这更像是一个概念而不是一个东西:一系列值(因此排序)可能存在也可能不存在(即流)。如果你说一个事物是一个序列,那么那个事物是否也必然是一个 Clojure 集合,甚至是一个序列集合?我想是的。

该顺序集合可能已经完全计算并且完全可用。或者它可能是根据需要生成值的“机器”(通过计算 - 可能以“纯”方式 - 或通过查询外部“不纯”、“口头”来源:键盘、数据库)

序列

这是一个东西:可以被函数处理的东西 firstrestnextcons(可能还有其他?),即遵循protocolclojure.lang.ISeq(这与“为接口提供实现”的概念大致相同) Java),即系统已经为一对(thing, function-name)注册了函数实现[我当然希望我做对了……]

这是基于函数seq?

  • 函数seq?可以用来测试这个
  • 相反,seq 是 seq? 返回 true 的任何内容。

seq? 的文档字符串:

如果 x 实现 ISeq,则返回 true

first 的文档字符串:

返回集合中的第一项。在其参数上调用 seq。 如果 coll 为 nil,则返回 nil。

rest 的文档字符串:

在第一个之后返回可能为空的项目序列。调用序列 论据。

next 的文档字符串:

返回第一个之后的项目的序列。在其参数上调用 seq。 如果没有更多项目,则返回 nil。

您在 seq 上调用 next 以生成下一个元素和新的 seq。重复直到获得nil

Clojure 的乐趣 将其称为“用于导航集合的简单 API”并说“seq 是实现 seq API 的任何对象”——如果“API”是“事物”(某种类型的)和对该事物起作用的功能。这取决于 API 概念的适当转变。

关于空seq的特殊情况

(def empty-seq (rest (seq [:x])))

(type? empty-seq)                 ;=> clojure.lang.PersistentList$EmptyList

(nil? empty-seq)                  ;=> false ... empty seq is not nil
(some? empty-seq)                 ;=> true ("true if x is not nil, false otherwise.")

(first empty-seq)                 ;=> nil   ... first of empty seq is nil ("does not exist"); beware confusing this with a nil in a nonempty list!
(next empty-seq)                  ;=> nil   ... "next" of empty seq is nil
(rest empty-seq)                  ;=> ()    ... "rest" of empty seq is the empty seq
   (type (rest empty-seq))        ;=> clojure.lang.PersistentList$EmptyList
   (seq? (rest empty-seq))        ;=> true
   (= (rest empty-seq) empty-seq) ;=> true

(count empty-seq)                 ;=> 0
(empty? empty-seq)                ;=> true

附录

函数seq

如果您将函数 seq 应用于有意义的事物(通常是顺序集合),您将获得一个表示/生成该集合成员的 seq。

文档字符串说:

返回集合的序列。如果集合为空,则返回 零。 (seq nil) 返回 nil。 seq 也适用于字符串,原生 Java 数组(引用类型)和任何实现 Iterable 的对象。 注意 seqs 缓存值,因此 seq 不应该用于任何 Iterable,其迭代器重复返回相同的可变对象。

申请seq后,可能会得到各种实际类的对象:

  • clojure.lang.Cons - 试试(class (seq (map #(* % 2) '( 1 2 3))))
  • clojure.lang.PersistentList
  • clojure.lang.APersistentMap$KeySeq
  • clojure.lang.PersistentList$EmptyList
  • clojure.lang.PersistentHashMap$NodeSeq
  • clojure.lang.PersistentQueue$Seq
  • clojure.lang.PersistentVector$ChunkedSeq

如果将seq应用于序列,则返回的事物的实际类可能与传入事物的实际类不同。它仍然是序列。

序列中的“元素”是什么取决于。例如,对于地图,它们是键值对,看起来像 2 元素 vector(但它们的实际类并不是真正的向量)。

函数lazy-seq

创建一个东西来懒惰地生成更多的东西(一个挂起的机器,一个挂起的流,一个thunk

文档字符串说:

获取返回 ISeq 或 nil 的表达式主体,并产生一个 仅在第一次 seq 时才会调用主体的 Seqable 对象 调用,并将缓存结果并在所有后续 seq 中返回 来电。另请参阅 - 实现了吗?”

关于“函数”和“事物”...和“对象”的注释

在 Clojure 世界中,我喜欢谈论“函数”和“事物”,而不是谈论“对象”,这是一个充满了 Java 特性和其他不良含义的术语。提及对象感觉就像是从底层 Java 世界中冒出来的碎片。

函数和事物有什么区别?

它是流动的!有些东西是纯函数,有些东西是纯东西,有些介于两者之间(可以作为函数使用,具有事物的属性)

特别是,Clojure 允许将关键字(事物)视为函数(在映射中查找值)或将映射(事物)解释为函数或函数的简写(获取键并返回值)的上下文关联到地图中的那个键)

显然,函数就是“first-class citizens”。

它也是上下文的!在某些情况下,函数变成了事物,或者事物变成了函数。

有一些令人讨厌的提及对象......这些是从底层 Java 世界中冒出来的碎片。

出于演示目的,集合图表

【讨论】:

  • Sorted map. Note: (sequential? (sorted-map :a 1) ;=> false,此行缺少尾随括号。顺便说一句,这个答案很清楚。谢谢!
【解决方案2】:

每个序列都是一个集合,但不是每个集合都是一个序列。

seq 函数可以将集合转换为序列。例如。对于地图,您将获得其条目的列表。不过,该条目列表与地图本身不同。

【讨论】:

  • 有趣,在 C# 中正好相反,每个collection 都是一个序列(它实现了IEnumerable),但不是每个sequence 都是一个集合。
  • @TimSchmelter 这与 Clojure 中的不同还是相同取决于哪个 Clojure Java 接口被认为等效于 .NET 的 IEnumerableSeqableISeq。 Clojure 集合实现了IPersistentCollection,它是Seqable 的子类型,Seqable 所需的唯一功能是我在回答中提到的seq。只有ISeq 的实现(又是IPersistentCollection 的子类型)在Clojure 中被视为序列,但可以说Seqable 的用途与.NET 的IEnumerable 相似。
【解决方案3】:

以下几点有助于理解collectionsequence之间的区别。

  1. “Collection”和“Sequence”是抽象,而不是可以从给定值确定的属性。

  2. 集合是价值的袋子。

  3. Sequence 是一种数据结构(集合的子集),预期以顺序(线性)方式访问。

下图最能说明它们之间的关系:

您可以阅读更多关于它的信息here

【讨论】:

【解决方案4】:

Clojure for the brave and true中,作者以一种非常容易理解的方式总结了它:

集合抽象与序列密切相关 抽象。 Clojure 的所有核心数据结构——向量、映射、 列表和集合——参与这两种抽象。

抽象的不同之处在于序列抽象是“关于” 在集合抽象时单独对成员进行操作 “关于”整个数据结构。例如,集合 函数countempty?every? 与任何个人无关 元素;它们是关于整体的。

【讨论】:

  • “Clojure 的所有核心数据结构——向量、映射、列表和集合——都参与了这两种抽象。”这实际上是错误的,因为“映射”和“集合”或不是“序列”。好吧,至少 sequential? 会为这些返回 false,即使是有序种类也是如此。
【解决方案5】:

对于seq?

如果 x 实现 ISeq,则返回 true

对于coll?

如果 x 实现 IPersistentCollection,则返回 true

我发现ISeq interface 扩展自 Clojure 源代码中的 IPersistentCollection,所以正如 Rörd 所说,每个序列都是一个集合。

【讨论】:

  • @Jerry101 很高兴您想添加到帖子中,但建议的编辑不是这样做的方法。要提出改进建议,最好添加注释来解释应该修复或更新的内容。另外,请不要多次建议对帖子进行相同的编辑。
【解决方案6】:

任何支持核心firstrest 函数的对象都是sequence

许多对象都满足这个接口,并且每个 Clojure 集合都提供至少一种 seq 对象,用于使用 seq 函数遍历其内容。

所以:

user> (seq [1 2 3])
    (1 2 3)

您也可以从map 创建一个序列对象

user> (seq {:a 1 :b 2})
    ([:a 1] [:b 2])

这就是为什么你可以在mapssets等上使用filtermapfor等。

因此您可以将许多类似集合的对象视为序列。

这也是为什么许多序列处理函数(例如filter)在输入上调用seq

 (defn filter
  "Returns a lazy sequence of the items in coll for which
  (pred item) returns true. pred must be free of side-effects."
  {:added "1.0"
   :static true}
  ([pred coll]
   (lazy-seq
      (when-let [s (seq coll)]

如果您拨打(filter pred 5)

  Don't know how to create ISeq from: java.lang.Long
                  RT.java:505 clojure.lang.RT.seqFrom
                  RT.java:486 clojure.lang.RT.seq
                 core.clj:133 clojure.core/seq
                core.clj:2523 clojure.core/filter[fn]

你看到seq调用是这个对象是一个序列验证。

如果您想深入了解,大部分内容都在Joy of Clojure 第 5 章中。

【讨论】:

  • 你也需要支持cons操作才能成为一个序列
  • 我已经为此付出了足够多的努力。 Clojure 的乐趣第 5 章需要重写。
猜你喜欢
  • 2010-11-08
  • 2010-11-05
  • 2012-09-03
  • 1970-01-01
  • 1970-01-01
  • 2017-08-25
  • 2016-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多