问题描述
我想创建一个函数,该函数创建其他函数,以记住它们被调用了多少次-不难。但是我想在将来的任意时间询问这些函数,而不是在调用之后立即询问。它可能像这样工作:
(defn mk-cntr []
(let [count (atom 0)
res (fn cntr [] (swap! count inc))]
res))
(let [f (mk-cntr)]
(f)
(f)
; other complicated stuff here
(f)
; Interrogate it Now.
(println (:count f))) ; or something similar
这确实会创建一些函数来计算它们被调用的次数,但是我不希望在调用后立即获得该信息。
我想重现以下JavaScript片段:
function counter() {
function result() { result.count++ }
result.count = 0
return result
}
这样,您可以执行let cntr = counter()
,然后稍后再像cntr.count
一样查询它,以查明它被调用了多少次。
我摸索了函数元数据,但无法提出有用的建议。感觉可以使用defrecord
和defprotocol
,但是想出一个在JavaScript中如此简单的东西似乎很麻烦。
Clojure / Script是否有一种简单的实现方法?
解决方法
如果您具有要跟踪的已知数量参数的固定函数,则可以创建一条记录并为所需的Arity实现IFn
:
(defrecord FC [counter]
IFn
(invoke [this]
(let [res (swap! counter inc)]
res)))
(defn mk-cntr []
(->FC (atom 0)))
如果您需要包装一些任意函数,我将创建一个包含包装函数和计数器的记录:
(defn mk-cntr2 [f]
(let [counter (atom 0)]
{:counter counter
:fn (fn [& args]
(swap! counter inc)
(apply f args))}))
然后您可以调用并查询:
(def f (mk-cntr2 println))
((:fn f) "first")
((:fn f) "second")
@((:counter f)) ;; 2
,
Metadata在这里很方便。
(defn counted [f]
(let [c (atom 0)]
(with-meta #(do (swap! c inc) (apply f %&))
{:count c})))
counted
包装一个函数,返回另一个在其元数据中保持调用计数的函数。按照您的示例样式:
(let [add (counted +)]
(println "add called" @(:count (meta add)) "times")
(println "add works as usual: 1+2 =" (add 1 2))
(println "add called" @(:count (meta add)) "times")
(println "sum of first 10 natural numbers is" (reduce add (range 11)))
#_more-complicated-stuff
(println "add called" @(:count (meta add)) "times"))
;; Prints the following
add called 0 times
add works as usual: 1+2 = 3
add called 1 times
sum of first 10 natural numbers is 55
add called 11 times
nil
user=>
add-watch
也是您可以订阅的计数器,可以这么说。
例外也是要考虑的事情。包装的fn抛出时必须怎么办?
,如果可以在代码库中使用Java,可以编写一个扩展AFn类并包装IFn的类。在您项目的Java源代码目录中(例如,您在Leiningen中可以有一行:java-source-paths ["javasrc"]
),在这里我有一个文件CountedFn.java
,内容如下:
import clojure.lang.AFn;
import clojure.lang.IFn;
import java.util.concurrent.atomic.AtomicInteger;
public class CountedFn extends AFn {
private IFn _inner;
private AtomicInteger _counter = new AtomicInteger(0);
public CountedFn(IFn inner) {
_inner = inner;
}
private void step() {
_counter.incrementAndGet();
}
public int getCount() {
return _counter.get();
}
public Object invoke(Object arg1) {
step();
return _inner.invoke(arg1);
}
// Implement 'invoke' for all other arities too... a bit of work.
}
然后像这样使用它:
(import 'CountedFn)
(defn add119 [x]
(+ x 119))
(def f (CountedFn. add119))
(f 10000)
;; => 10119
(.getCount f)
;; => 1
(f 1000)
;; => 1119
(.getCount f)
;; => 2