Search code examples
clojure

Arity exception iterating and filtering list


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?


Solution

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