Arity 异常迭代和过滤列表

Arity exception iterating and filtering list

我正在尝试迭代列表并过滤掉所有符合特定条件的元素。到目前为止,我使用 map 函数,并且在每次迭代时,我都会检查该元素是否应该被过滤。

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

问题是我无法在过滤器的匿名函数中使用地图匿名函数的变量“代码”,因为我得到以下 arity 异常:

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

如何在匿名函数中访问外部变量?

这段代码说明了问题和解决方案:

; 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

您的匿名函数 #(println "hello") 不接受任何参数。如果您尝试使用 arg 调用它,它将失败,如下所示:

(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

当使用 mapmapv 时,它会为集合中的每个元素调用一次您的函数,因此您会得到与 (yo! 42).

相同的异常

旁注:

大多数人建议您不要嵌套多个匿名函数(您的问题有 2 个匿名 fns),无论它们是像 (fn ...) 还是使用 reader 宏 #(...) 定义的( reader 宏被转换为 fn 形式)。

无论哪种方式,它都太难读了,而且错误信息也难以辨认(正如你刚刚遇到的!)

一个对匿名函数有用的技巧是像这样“标记”它们:

(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
  )

异常如下所示:


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>

您可以看到堆栈跟踪中嵌入的函数标签 my_secret_fn

tst.demo.core$fn__22852$my_secret_fn__22859.invoke

堆栈跟踪还有助于在我的编辑器中包含 my_secret_fncore.cljcline 26)中的错误行号。

你得到 ArityException 是因为 filter 函数的第一个参数需要一个单一参数的函数。例如,

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

会抛出 ArityException,但是

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

不会。

如果您只是想过滤掉(又名 remove)序列中与特定谓词匹配的元素,请使用 remove,如下例所示,它会删除所有正值来自序列。

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