为什么clojure中有这么多映射构造函数?

为什么clojure中有这么多映射构造函数?,clojure,Clojure,新手的问题,但我真的不明白为什么在clojure中有这么多构建地图的操作 您有conj、assoc和merge,但它们似乎或多或少做了相同的事情 (assoc {:a 1 :b 2} :c 3) (conj {:a 1 :b 2} {:c 3}) (merge {:a 1 :b 2} {:c 3}) 真正的区别是什么?为什么当它们做或多或少相同的事情时需要所有这些方法 由于地图在Clojure中是如此普遍的数据结构,因此有多种工具来操作它们是有意义的。在稍微不同的情况下,各种不同的功能在语法上

新手的问题,但我真的不明白为什么在clojure中有这么多构建地图的操作

您有
conj
assoc
merge
,但它们似乎或多或少做了相同的事情

(assoc {:a 1 :b 2} :c 3)
(conj {:a 1 :b 2} {:c 3})
(merge {:a 1 :b 2} {:c 3})

真正的区别是什么?为什么当它们做或多或少相同的事情时需要所有这些方法

由于地图在Clojure中是如此普遍的数据结构,因此有多种工具来操作它们是有意义的。在稍微不同的情况下,各种不同的功能在语法上都很方便

我个人对您提到的具体功能的看法:

  • 我使用assoc向给定键和值的映射添加单个值
  • 我使用合并合并两个地图或一次添加多个新条目
  • 我根本不把conj用于地图,因为我在心里把它与列表联系起来

assoc
conj
对于其他数据结构的行为非常不同:

user=> (assoc [1 2 3 4] 1 5)
[1 5 3 4]
user=> (conj [1 2 3 4] 1 5)
[1 2 3 4 1 5]
如果您正在编写一个可以处理多种类型集合的函数,那么您的选择将带来很大的不同

merge
视为仅映射的函数(类似于其他集合的
conj

我的意见是:

  • 关联-在“更改”现有键/值对时使用
  • conj-在“添加”新键/值对时使用
  • 合并-在合并两个或多个地图时使用

实际上,与地图一起使用时,这些函数的行为完全不同

  • conj

    首先,问题文本中的
    (conj{:a1:b2}:c3)
    示例根本不起作用(1.1和1.2都不起作用;
    illegargumentexception
    )。只有少数类型可以连接到地图上,即两个元素向量,
    clojure.lang.MapEntry
    s(基本上相当于两个元素向量)和地图

    请注意,地图的
    seq
    由一组
    MapEntry
    s组成。因此,你可以这样做

    (into a-map (filter a-predicate another-map))
    
    (请注意,
    into
    尽可能在内部使用
    conj
    ——或
    conj!
    )。无论是
    merge
    还是
    assoc
    都不允许您这样做

  • 合并

    这几乎完全等同于
    conj
    ,但它将其
    nil
    参数替换为
    {}
    ——空哈希映射——因此,当链中的第一个“映射”恰好是
    nil
    时,将返回一个映射

    (apply conj [nil {:a 1} {:b 2}])
    ; => ({:b 2} {:a 1}) ; clojure.lang.PersistentList
    (apply merge [nil {:a 1} {:b 2}])
    ; => {:a 1 :b 2} ; clojure.lang.PersistentArrayMap
    
    注意,没有任何东西(除了docstring…)可以阻止程序员将
    merge
    与其他集合类型一起使用。如果一个人这样做了,奇怪就会接踵而至;不推荐

  • assoc

    同样,问题文本中的示例--
    (assoc{:a1:b2}{:c3})
    --将不起作用;相反,它将抛出一个
    非法argumentException
    assoc
    接受一个map参数,后跟偶数个参数——奇数位置的参数(假设map位于位置0)是键,偶数位置的参数是值。我发现我将事物关联到地图上的次数比我将事物关联到地图上的次数要多,尽管当我将事物关联到地图上时,
    assoc
    会觉得很麻烦

  • 合并:

    为了完整起见,这是处理映射的最后一个基本函数。我觉得它非常有用。它按照docstring的指示工作;下面是一个例子:

    (merge-with + {:a 1} {:a 3} {:a 5})
    ; => {:a 9}
    
    请注意,如果一个映射包含一个“new”键,而该键在它左边的任何映射中都没有出现,则不会调用merging函数。这有时令人沮丧,但在1.2中,一个聪明的
    具体化
    可以提供一个带有非
    nil
    默认值的映射


  • merge
    获取任意数量的映射并将它们合并,而不仅仅是两个。
    merge
    conj
    在处理
    nil
    (转换为
    {}
    )时有所不同,不管其他参数的类型如何。此外,我对
    assoc
    conj
    问题有不同的处理方法,但由于我已经发布了一个单独的答案,我将不在此详述。:-)conj还可以处理任意数量的贴图。user=>(conj{:a1:b2}{:c3}{:d4}{:d4,:c3,:a1,:b2}如何将
    合并到
    中?为了说明
    合并的最后一点,我准备了以下要点:感谢您的解释。正如您所看到的,我出现了一个键入错误,并在conj和assoc之间切换了第二个参数。我认为惯用的方法是使用与要添加到映射中的新项最初可用的形式相匹配的函数;如果是作为映射,请使用
    merge
    ,如果是作为未在集合中组合的一组键和值,请使用
    assoc
    等。不过,我真正想指出的是,那就是
    conj
    是通用的Clojure数据结构构建函数——你不能真的把它放在列表抽屉里。@Michal也许你是对的——但是出于某种原因,我不相信在不同的输入类型上做语义上非常不同的事情而没有警告的函数:-)我认为
    conj
    确实可以不同输入类型上的语义等价操作。;)还有
    (进入{:a1:b2}{:c3})