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