关于字典:Clojure中使用map和匿名函数的异常

Exception using map and anonymous functions in Clojure

我在弄乱Clojure地图,发现这种情况我无法理解。

比方说我有一张这样的地图:

1
(def map-test {:name"head" :size 3})

我想更改此地图的值,在Clojure中,通常的方法是使用修改后的数据生成一个新地图。

所以我有这个功能:

1
2
3
(defn map-change
  [part]
  {:name (str (:name part)"-" 1) :size (:size part)})

按预期,调用(map-change map-test)返回:{:name"head-1", :size 3}

因此,我使用map编写了此函数,以将哈希映射克隆给定次数,例如{:name"head-1" ...}{:name"head-2" ...}{:name"head-3" ...}等:

1
2
3
(defn repeat-test
  [part times]
  (map #({:name (str (:name part)"-" %) :size (:size part)}) (range 1 (inc times))))

但是当我呼叫(repeat-test map-test 5)时,我有一个无法理解的异常:

Wrong number of args (0) passed to: PersistentArrayMap

在评估(:size part)=>3

之后立即将值分配给:size时,调试器将引发此异常

这是堆栈跟踪的最后一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   Unhandled clojure.lang.ArityException
   Wrong number of args (0) passed to: PersistentArrayMap

                  AFn.java:  429  clojure.lang.AFn/throwArity
                  AFn.java:   28  clojure.lang.AFn/invoke
                      REPL:   80  clj-lab-00.hobbits/repeat-test/fn
                  core.clj: 2644  clojure.core/map/fn
              LazySeq.java:   40  clojure.lang.LazySeq/sval
              LazySeq.java:   49  clojure.lang.LazySeq/seq
                   RT.java:  521  clojure.lang.RT/seq
                  core.clj:  137  clojure.core/seq
            core_print.clj:   46  clojure.core/print-sequential
            core_print.clj:  153  clojure.core/fn
            core_print.clj:  153  clojure.core/fn
              MultiFn.java:  233  clojure.lang.MultiFn/invoke
                  core.clj: 3572  clojure.core/pr-on
                  core.clj: 3575  clojure.core/pr
                  core.clj: 3575  clojure.core/pr
                  AFn.java:  154  clojure.lang.AFn/applyToHelper
....

但是如果我使用的非匿名函数执行与匿名函数相同的操作:

1
2
3
4
5
6
7
(defn map-change
  [part i]
  {:name (str (:name part)"-" i) :size (:size part)})

(defn repeat-test
  [part times]
  (map #(map-change part %1) (range 1 (inc times))))

现在调用(repeat-test map-test 5)即可。为什么呢我想念什么?


该错误类似于此简化示例:

1
2
3
(map #({:a %}) [1 2 3])

clojure.lang.ArityException: Wrong number of args (0) passed to: PersistentArrayMap

您可以展开#({:a %})以查看实际正在编译和执行的代码:

1
2
(macroexpand '#({:a %}))
;;=> (fn* [p1__21110#] ({:a p1__21110#}))

换句话说,#({:a %})扩展为(fn [x] ({:a x}))之类的东西。该函数主体中的问题在于,该映射被称为没有参数的函数。

地图的行为类似于功能:它们是键的功能。但是他们期望至少一个论点,最多两个论点:

1
2
({:a 1} :a) ;;=> :a
({:a 1} :b 2) ;;=> 2

您根本不打算将地图作为函数来调用。您只想拥有地图。匿名函数文字总是扩展为函数调用,并且不能产生直接值。您可以通过以下几种方法解决此问题:

  • #(-> {:a %})
  • #(identity {:a %})
  • #(hash-map :a %)
  • #(do {:a %})
  • (fn [x] {:a x})

我更喜欢后者,尽管我发现第一个很有趣。就像在说:我要退还我指向的东西。