【问题标题】:Exception using map and anonymous functions in Clojure在 Clojure 中使用映射和匿名函数的异常
【发布时间】:2016-10-08 20:57:51
【问题描述】:

我在玩 Clojure 映射时发现了这种我无法理解的情况。

假设我有一张这样的地图:

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

我想改变这个映射的值,在 Clojure 中通常的方法是用修改后的数据生成一个新的。

所以我有这个功能:

(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" ...} 等:

(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 时引发此异常

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

   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
....

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

(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) 有效。为什么 ?我错过了什么?

【问题讨论】:

    标签: dictionary exception clojure


    【解决方案1】:

    错误类似于这个简化的例子:

    (map #({:a %}) [1 2 3])
    
    clojure.lang.ArityException: Wrong number of args (0) passed to: PersistentArrayMap
    

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

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

    换句话说,#({:a %}) 扩展为类似于(fn [x] ({:a x}))。这个函数体中的问题是 map 是作为一个函数调用的,没有参数。

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

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

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

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

    我更喜欢后者,虽然我觉得第一个很有趣。这就像在说:我要返回我指向的东西。

    【讨论】:

    • 我会添加 #(do {:a %}) 作为选项。
    • 所以初学者的经验法则是使用(fn [x] ...) 并避免使用#() 形式,至少在我能更好地理解宏之前。
    • @OlegTheCat 这也是一种可能。我添加了它。还有更多选择:#(and {:a %})#(or {:a %}) :-)。 @Marcs我认为这是一个很好的经验法则。还有一点需要注意:匿名函数字面量不能嵌套。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-15
    • 2020-01-03
    • 2023-01-30
    • 1970-01-01
    • 2017-07-17
    • 1970-01-01
    相关资源
    最近更新 更多