0

Why does this work:

(mapcat (fn [x] x) [[1] [2 3] [[4 5 6]]])

But this results in an arity error?

(mapcat #(%) [[1] [2 3] [[4 5 6]]])

The error is:

ArityException Wrong number of args (0) passed to: PersistentVector clojure.lang.AFn.throwArity (AFn.java:429)

heneryville
  • 2,853
  • 1
  • 23
  • 30

2 Answers2

4

#(%) is equivalent to (fn [x] (x)) and not (fn [x] x). This is the core of the problem. Whenever you're not sure what a particular piece of reader syntax does, read-string comes in extremely handy. Try (read-string "#(%)") in your REPL. It should print something like (fn* [p1__3#] (p1__3#)). The p1__3# is a free-variable, in the sense that we can replace it with any other unqualified symbol, and it would be equivalent.

As far as the ArityException is concerned, vectors can be called as functions. They are functions of they indices. ([:a :b :c] 1) returns :b, for example. When called as functions, only arity-1 is legit. In this case mapcatting with #(%) calls each of the vectors in your input without any arguments. This is what causes the ArityException.

Also, (mapcat (fn [x] x) ...) is the same as (mapcat identity ...) is the same as (apply concat ...). You might want to use this instead.

jaihindhreddy
  • 358
  • 2
  • 7
0

You are looking for:

(mapcat #(identity %) [[1] [2 3] [[4 5 6]]])

This shows the progression:

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

(dotest
  (let [data [[1]
              [2 3]
              [[4 5 6]]]]
    (is= [1 2 3 [4 5 6]]
      (mapcat (fn [x] x) data)
      (mapcat (fn [x] (identity x)) data)
      (mapcat #(identity %) data))))

When you use #(%) you are essentially saying:

([2])     ; missing arg

Here is how to understand it

(dotest
  (let [data [0 1 2 3 4]]
    (is= 2 (get data 2))
    (is= 2 (data 2))
    (is= 2 ([0 1 2 3 4] 2))
    (throws? ([0 1 2 3 4]))))

In clojure, seeing parantheses like (xxxxx) means "function call". Just like in Java, seeing xxxxx() means "function call". Parens in Clojure never mean grouping (I know, it is a hard habit to get out of!).

A demo:

(dotest
  (let [the-answer-fn (fn [& args] 42)
        fn-identity   (fn [x] x)
        fn-caller     (fn [x] (x))]
    (is= 42 (the-answer-fn))
    (is= 3 (identity 3))
    (is= 3 (fn-identity 3))
    (is= 42 (fn-caller the-answer-fn))
    (is (fn? #(the-answer-fn))) ; it always returns a function
    (throws? (fn-caller 3))))

Note that the #(xxx) reader macro is just a shorthand notation for

(fn [] (xxx))

So, it invokes it's body. As a memory aid, just pretend the # isn't there. The remaining part shows what happens.

Also, remember that you are returning a new function that wraps the (xxx) function call.

Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
  • Why? Isn't #(%) an identity function? – heneryville Jul 20 '20 at 16:01
  • Hmm. This sent me down the road to realize that #(%) != (fn [x] x). Specifically (#(%) 3) throws a cast exception, but ((fn [x] x) 3) does not. It appears that the #() macro will invoke it's body, where fn will not. – heneryville Jul 20 '20 at 16:31
  • See update to answer. – Alan Thompson Jul 20 '20 at 17:44
  • 2
    @AlanThompson You've surely been on Stack Overflow for long enough to have seen this question asked before; or if not, to guess that it has been asked before. When a duplicate question is asked, prefer to spend a few moments finding the best duplicate, and close the new question pointing to the older one. It's better to make it easy to find the existing high-quality answers than to have answers scattered across the site. – amalloy Jul 20 '20 at 18:15