Clojure函数文字#()得到了

时间:2014-07-24 16:31:41

标签: macros clojure

我正在重构我的代码以使其更简单。这种演变是这样的,从以下开始:

(defn board-changed
      "called when current-board changes"
  [fr _ _ _ _]
  (repaint! (select fr [:#canvas])))

然后在另一个函数中:

.
.
(add-watch the-board :board (partial board-changed fr))
.
.

我决定我并不真的需要一个单独的功能,更换板,我可以在内联中匿名定义它,并在此过程中删除(部分)的需要,并且不需要每次都查找画布时间,这导致了这段代码:

.
.
(let [cnv (select fr [:#canvas])]
  (add-watch the-board :board (fn [_ _ _ _] (repaint! cnv))))
.
.

此代码有效。

我决定下一步是使用#()宏进一步简化,但我发现下面的代码不起作用:

.
.
(let [cnv (select fr [:#canvas])]
  (add-watch the-board :board #(repaint! cnv)))
.
.

它默默地失败:第一次调用手表时程序会掉入黑洞:没有错误或例外。

出了什么问题?

3 个答案:

答案 0 :(得分:3)

这是how agent errors work。你第一次失败是缓存,下一次会立即发生。

  

如果动作函数抛出任何异常,则不会发生嵌套调度,并且异常将缓存在代理本身中。当代理程序缓存了错误时,任何后续交互都将立即引发异常,直到代理程序的错误被清除。可以使用代理错误检查代理错误,并使用restart-agent重新启动代理。

您可以使用agent-error检查首次失败错误,或使用set-error-handler!

注册错误处理程序
so.core=> (def a (agent 0))
#'so.core/a

so.core=> (add-watch a :key #(println "foo"))
#<Agent@766245a4: 0>

so.core=> (set-error-handler! a (fn [the-agent the-exception] (println "bar")))
nil

so.core=> (send a inc)
#<Agent@766245a4: 1>
bar

so.core=> (agent-error a)
#<ArityException clojure.lang.ArityException: 
  Wrong number of args (4) passed to: core/eval1299/fn--1300>

so.core=> (send a inc)
ArityException 
  Wrong number of args (4) passed to: core/eval1299/fn--1300  
  clojure.lang.AFn.throwArity (AFn.java:429)

答案 1 :(得分:3)

add-watch需要4个参数的函数。使用不带参数文字的匿名函数文字#(...)会使读者产生一个无参数的函数。

为节省空间,您可以撰写(fn [& _] ...)

答案 2 :(得分:1)

我在传递无效参数之前就已经看到过这样的行为了:你要么得到一个例外,因为你传递了一个意想不到的类型,或者你传递了一个无效的数字参数(奇怪的是,似乎并不总是抛出异常!)。

事实证明这是后者的一个例子:错误的参数数量。但为什么呢?

我做的第一件事是宏扩展:

(clojure.pprint/pprint (macroexpand '#(repaint! cnv)))
=> (fn* [] (repaint! cnv))

这给了我第一个线索:我在函数参数中假设#()灵活性,在运行时提供了将它们称为%,%1,%2等的能力。

宏观扩展下面的表格确认了它:

(clojure.pprint/pprint (macroexpand '(add-watch the-board :board #(repaint! %4))))
=> (fn* [p1__7764# p2__7765# p3__7766# p4__7763#] (repaint! p4__7763#))

#()查看具有最高编号的%的正文,在本例中为4,并假设该函数有很多参数,因此在这种情况下为4.(那些有趣的参数名称是gensyms)。 / p>

所以我遇到的问题是我传递给add-watch的函数必须使用4个参数,但#()生成的函数为零。

所以结论是(fn arg-list body)只能在主体引用传入的最后一个参数时用#(body)替换。

所以

(fn [_ _ _ arg4] (println arg4))

可以替换为

#(println %4)

但是

(fn [_ _ _ _] (println "hello world")

无法替换

#(println "hello world")

我必须承认,我很惊讶您没有获得运行时间异常!