1

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 ?

1 Answer 1

6

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] ({:a x})). 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] {:a x})

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.

Sign up to request clarification or add additional context in comments.

3 Comments

I'd add #(do {:a %}) as an option.
So the rule of thumbs for a beginner is to use (fn [x] ...) and avoid the #() form at least until I'll understand macros better.
@OlegTheCat That's also a possibility. I added it. There are many more options: #(and {:a %}), #(or {:a %}) :-). @Marcs I think that is a good rule of thumb. One other point to note: anonymous function literals can't be nested.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.