Arity exception iterating and filtering list

Viewed 116

I am trying to iterate a list and filter out all the elements that match a particular criteria. So far, I use the map function and, at every iteration, I will check whether that element should be filtered or not.

(defn stock-list [book-list cat-list]
  (map (fn [code] (filter #(println code) book-list)) cat-list)
)

The problem is that I can't use the variable "code" of the map anonymous function in the filter's one since I get the following arity exception:

Error printing return value (ArityException) at clojure.lang.AFn/throwArity (AFn.java:429).
Wrong number of args (1) passed to: github-excercises.git-excercises/stock-list/fn--5659/fn--5660

How can I access an outer variable in an anonymous function?

2 Answers

This code illustrates the problem & solution:

; throws exception:  Wrong number of args (1) passed to: tst.demo.core/fn--22359/fn--22360
; (mapv #(println "hello") [1 2 3])

; works
(mapv #(println "hello" %) [1 2 3]) ; not 'map' since it is lazy

  ; result =>
  ;   hello 1
  ;   hello 2
  ;   hello 3

Your anonymous function #(println "hello") does not accept any args. If you try to call it with an arg, it fails as below:

(defn yo! [] "Yo!")

(yo!) ;=> "Yo!"  as expected

(yo! 42)   ; try to call it with an arg
;   => clojure.lang.ArityException: Wrong number of args (1) passed to:
;         tst.demo.core/fn--22401/yo!--22406

When using map or mapv, it calls your function once for each element in the collection, so you get the same exception as (yo! 42).


Side Note:

Most people recommend you don't nest multiple anonymous functions (your question has 2 anonymous fns), whether they are defined like (fn ...) or with the reader macro #(...) (the reader macro is converted into the fn form).

Either way, it is too hard to read, and the error messages are indecipherable (as you have just encountered!)

One trick that is helpful with anonymous functions is to "label" them like so:

(let [myfn (fn my-secret-fn  ; <= add a function "label" before the arglist
             [x]
             (println :result (/ 1 x)))]
  
  (myfn 2)     ; => ":result 1/2"
  (myfn 0)     ; => throws an exception
  )

The exception looks like this:


ERROR in (dotest-line-9) (Numbers.java:188)
Uncaught exception, not in assertion.
expected: nil
  actual: java.lang.ArithmeticException: Divide by zero
 at clojure.lang.Numbers.divide (Numbers.java:188)
    clojure.lang.Numbers.divide (Numbers.java:3877)
    tst.demo.core$fn__22852$my_secret_fn__22859.invoke (core.cljc:26)
    tst.demo.core$fn__22852.invokeStatic (core.cljc:28)
    <snip>

and you can see the function label my_secret_fn embedded in the stack trace:

tst.demo.core$fn__22852$my_secret_fn__22859.invoke

The stacktrace also helpfully includes the line number of the error in my_secret_fn (line 26 of core.cljc) in my editor).

You are getting the ArityException because the first argument to the filter function expects a function of a single parameter. For example,

(filter #(println 123) [1 2 3 4])

will throw the ArityException, but

(filter #(println %) [1 2 3 4])

will not.

If you are trying to just filter out (aka remove) elements of a sequence that match a particular predicate, then use remove, as in the following example, which removes all positive values from the sequence.

user> (remove pos? [1 -1 -2 -3 4 -1 5])
(-1 -2 -3 -1)
Related