通过List或Seq上的数字索引进行Clojure关联解构:意外结果

时间:2019-04-28 20:03:07

标签: clojure

Clojure的Associative Destructuring允许通过数字索引解构一个 vector (可能还有一个 seq list )。

到目前为止,clojure.org尚未提及此模式,但第二版的 Clojure的喜悦中提到了 。迈克尔·福格斯(Michael Fogus),克里斯·豪斯(Chris Houser),2014年5月,第14页。 59.这种方法出现在“关联解构”一节中-错误,因为这是基于索引的解构,这只是“关联解构”的特例,在上述书中,它被称为“带地图的解构”。 / p>

无论如何,结果出乎意料(Clojure 1.10.0):

在所有情况下,都提取索引0和3处的值。

这些工作符合预期:

(let [{firstthing 0, lastthing 3} [1 2 3 4]] [firstthing lastthing])
;=> [1 4]

(let [{firstthing 0, lastthing 3} (vec '(1 2 3 4))] [firstthing lastthing])
;=> [1 4]

但在列表上:

(let [{firstthing 0, lastthing 3} '(1 2 3 4)] [firstthing lastthing])
;=> [nil 4]

为什么位置0处有nil

类似地:

(let [{firstthing 0, lastthing 3} (seq '(1 2 3 4))] [firstthing lastthing])
;=> [nil 4]

但另一方面:

(let [{firstthing 0, lastthing 3} (vec (seq '(1 2 3 4)))] [firstthing lastthing])
;=> [1 4]

这是怎么回事?

附录:

(let [{firstthing 0, lastthing 3} { 1 2 3 4 } ] [firstthing lastthing])
;=> [nil 4]

...听起来很合理,因为要进行关联破坏的地图实际上是{1 2, 3 4}。因此,不是通过 position 而是通过 integer键(可以改变脚下表达式的含义)进行查找的结果将精确地为{{1} }。不是矢量的东西会首先注入地图吗?

[nil 4]

看起来确实很像。...

(let [{firstthing 10, lastthing 30} (seq '(10 2 30 4))] [firstthing lastthing])
;=> [2 4]

哦,是的。

2 个答案:

答案 0 :(得分:5)

  

为什么位置0处为零?

(let [{firstthing 0, lastthing 3} '(1 2 3 4)] [firstthing lastthing])
;=> [nil 4]

如果您查看为该let生成的代码:

user=> (macroexpand '(let [{firstthing 0, lastthing 3} '(1 2 3 4)] [firstthing lastthing]))
(let*
  [map__8012 (quote (1 2 3 4))
   map__8012 (if (clojure.core/seq? map__8012)
               (clojure.lang.PersistentHashMap/create (clojure.core/seq map__8012))
               map__8012)
   firstthing (clojure.core/get map__8012 0)
   lastthing (clojure.core/get map__8012 3)]
  [firstthing lastthing])

您会看到,如果使用seq?,它将转换为地图。所以:

user=> (def map__8012 (quote (1 2 3 4)))
#'user/map__8012
user=> (clojure.core/seq? map__8012)
true
user=> (clojure.lang.PersistentHashMap/create (clojure.core/seq map__8012))
{1 2, 3 4}

因此,对于密钥nil,您将获得0;对于密钥4,您将获得3

答案 1 :(得分:1)

简短的答案是,只有地图和矢量是关联的。列表和序列不具有关联性。地图解构仅针对关联结构进行定义:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(dotest
  (let [mm {:a 1 :b 2}
        vv [1 2 3]
        ll (list 1 2 3)
        sv (seq vv)
        sl (seq ll) ]
    (spyx (associative? mm))
    (spyx (associative? vv))
    (spyx (associative? ll))
    (spyx (associative? sv))
    (spyx (associative? sl)) ) )

结果:

(associative? mm) => true
(associative? vv) => true
(associative? ll) => false
(associative? sv) => false
(associative? sl) => false

Clojure经常采取(垃圾回收,垃圾回收)的态度(相当苛刻),而不是在调用带有无效参数的函数时抛出异常。

甚至还有a warning in ClojureDocs.org

我认为默认情况下,函数应更具防弹性(即检查arg值/类型),并且仅将精简版本提供为clojure.core.raw/get或类似名称。


更新

基于上面@cfrick的回答,我们看到了这种情况的起源。由于序列不是图谱,因此必须先将序列转换为图谱,然后才能进行解构。因此,let假定您提供了一系列键值对,例如[k1 v1 k2 v2 ...],这些键值对应像以下那样转换为映射:

(apply hash-map [k1 v1  k2 v2  ...])

如果这不是您的意图,则应通过以下方式将序列显式转换为关联对象:

(zipmap (range) <the-seq>)

或更简单地说:

(vec <the seq>)

或者,您可以为clojure提供假定存在的键值序列:

(list 0 1  1 2  2 3  3 4)

请注意,如果您的序列具有(list 1 2 3)之类的奇数长度,则在从seq到map的隐式转换过程中会出错。