为什么clojure系列没有';不能直接实现ISeq接口吗?

为什么clojure系列没有';不能直接实现ISeq接口吗?,clojure,sequence,seq,Clojure,Sequence,Seq,clojure中的每一个集合都被称为“可排序”,但实际上只有列表和CON是顺序: user> (seq? {:a 1 :b 2}) false user> (seq? [1 2 3]) false 所有其他方法首先将集合转换为序列,然后才对其进行操作 user> (class (rest {:a 1 :b 2})) clojure.lang.PersistentArrayMap$Seq 我不能做这样的事情: user> (:b (rest {:a 1 :b 2

clojure中的每一个集合都被称为“可排序”,但实际上只有列表和CON是顺序:

user> (seq? {:a 1 :b 2})
false
user> (seq? [1 2 3])
false    
所有其他方法首先将集合转换为序列,然后才对其进行操作

user> (class (rest {:a 1 :b 2}))
clojure.lang.PersistentArrayMap$Seq
我不能做这样的事情:

user> (:b (rest {:a 1 :b 2}))
nil
user> (:b (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}))
nil
并且必须强制返回到具体的数据类型。对我来说,这似乎是一个糟糕的设计,但很可能我还没有理解它


那么,为什么clojure集合不直接实现ISeq接口,而所有seq函数都不返回与输入对象属于同一类的对象呢?

seq是有序结构,而映射和集合是无序的。两个值相等的映射可能具有不同的内部顺序。例如:

user=> (seq (array-map :a 1 :b 2))
([:a 1] [:b 2])
user=> (seq (array-map :b 2 :a 1))
([:b 2] [:a 1])
要求映射的
rest
是没有意义的,因为它不是顺序结构。这同样适用于一套

那么向量呢?它们是按顺序排列的,所以我们可以潜在地映射到一个向量上,实际上有这样一个函数:
mapv

你可能会问:为什么这不是隐含的?如果我将一个向量传递给
map
,为什么它不返回一个向量

首先,这意味着对向量这样的有序结构做一个例外,而Clojure并不擅长做例外


但更重要的是,你会失去seqs最有用的特性之一:懒惰。将诸如
map
filter
等seq函数链接在一起是一种非常常见的操作,如果没有惰性,这将大大降低性能并占用大量内存。

序列是一种逻辑列表抽象。它们提供对(稳定的)有序值序列的访问。它们作为集合上的视图实现(具体接口与逻辑接口匹配的列表除外)。序列(视图)是一个单独的数据结构,它引用到集合中以提供逻辑抽象

序列函数(map、filter等)获取一个“seqable”对象(可以生成序列的东西),调用seq来生成序列,然后对该序列进行操作,返回一个新序列。是否需要或如何将该序列重新收集回具体的集合取决于您。向量和列表是有序的,而集合和映射不是有序的,因此这些数据结构上的序列必须计算并保持集合之外的顺序


像mapv、filterv、reduce kv这样的专门功能使您能够“在收藏中”当您知道希望操作在末尾而不是顺序返回集合时。

集合类遵循工厂模式,即它们没有实现
ISeq
,而是实现
sequeable
,即您可以从集合创建一个
ISeq
,但集合本身不是一个
ISeq

现在,即使这些集合直接实现了
ISeq
,我也不确定这将如何解决通用序列函数返回原始对象的问题,因为这毫无意义,因为这些通用函数应该在
ISeq
上工作,他们不知道是哪个物体给了他们这个
ISeq

java中的示例:

interface ISeq {
    ....
}

class A implements ISeq {

}

class B implements ISeq {

}

static class Helpers {
    /*
        Filter can only work with ISeq, that's what makes it general purpose.
        There is no way it could return A or B objects.
    */
    public static ISeq filter(ISeq coll, ...) { } 
    ...
}

这已经在Clojure google group上讨论过了;例如,请参见今年2月的线程。我将冒昧地在下面的帖子中重复使用我在邮件中提出的一些观点,同时添加几个新的观点

在我继续解释为什么我认为“seq分离”设计是正确的之前,我想指出一个自然的解决方案,适用于那些您真的希望有一个类似于输入的输出而不需要明确说明它的情况,它以contrib库中的函数
fmap
的形式存在。(我认为默认情况下使用它不是一个好主意,但是,出于与核心库设计相同的原因。)

概述 我认为关键的观察结果是,序列操作,如
map
filter
等,在概念上分为三个独立的关注点:

  • 迭代输入的某种方式

  • 将函数应用于输入的每个元素

  • 产生产出

  • 显然是2。如果我们能处理1,这是没有问题的。三,。让我们来看看这些

    迭代

    1。考虑到最简单和最有效的迭代集合的方式通常不涉及分配与集合相同的抽象类型的中间结果。将一个函数映射到一个向量上的分块seq上可能比将一个函数映射到一个seq上的性能要高得多,从而为每次调用

    next
    生成“视图向量”(使用
    subvec
    );然而,后者是Clojure风格向量上的
    next
    在性能方面所能做的最好的方法(即使在存在的情况下,当我们需要一个合适的子向量/向量切片操作来实现一个有趣的算法时,这是非常好的,但是如果我们使用它们来实现
    next
    ,遍历速度会非常慢)

    在Clojure中,专门的seq类型维护遍历状态和额外功能,例如(1)用于排序映射和集合的节点堆栈(除了更好的性能外,这比使用
    dissoc
    /
    disj
    !)的遍历具有更好的大O复杂性),(2)用于将叶数组包装成向量块的当前索引+逻辑,(3)哈希映射的遍历“延续”。通过这样一个对象遍历集合比通过
    subvec
    /
    dissoc
    /
    disj
    进行任何尝试都要快

    但是,假设我们愿意接受在向量上映射函数时的性能损失。好吧,现在让我们尝试过滤:

    (->> some-vector (map f) (filter p?))
    
    这里有一个问题——没有好的方法从向量中移除元素。(同样,RRB树c
    (->> some-sorted-set (filter p?) (map f) (take n))
    
    (require '[clojure.core.reducers :as r])
    
    (->> some-set (r/map f) (r/filter p?) (into #{}))