简体   繁体   中英

Exception using map and anonymous functions in Clojure

I was messing around with Clojure maps and I discovered this situation that I can't understand.

Let's say I got a map like this:

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

I want to change the value of this map, in Clojure the usual way is to generate a new one with the modified data.

So I have this function:

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

As expected the invocation (map-change map-test) returns: {:name "head-1", :size 3}

So I wrote this function using map to clone the hash-map a given number of times, like this {:name "head-1" ...}{:name "head-2" ...}{:name "head-3" ...} etc:

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

But I got an exception that I cannot understand when I call (repeat-test map-test 5) :

Wrong number of args (0) passed to: PersistentArrayMap

The debugger throws this exception when is assigning the value to :size right after has evaluated (:size part)=>3

Here's the last part of the stacktrace:

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

But if I use a non anonymous function that does the same operation as the anonymous one:

(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))))

Now calling (repeat-test map-test 5) works. Why ? What am I missing ?

The error is similar to this simplified example:

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

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

You can expand #({:a %}) to see what code is actually being compiled and executed:

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

In other words, #({:a %}) expands to something like (fn [x] ({:ax})) . The problem in the body of this function is that the map is called as a function, with no arguments.

Maps behave like functions: they are functions of their keys. But they expect at least one argument and at most two:

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

You didn't intend to call the map as a function at all. You just wanted to have the map. The anonymous function literal always expands into a function call and cannot yield a direct value. You can solve this several ways:

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

I prefer the latter, although I find the first one pretty funny. It's like saying: I want to return the thing I'm pointing at.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM