一个创建函数的函数,可以查询它们被调用的次数

A function that creates functions that can be interrogated for the number of times they have been called

我想创建一个函数来创建其他函数,这些函数会记住它们被调用了多少次 -- 并不难。但我想在未来的某个任意时间询问这些功能,而不是在被调用后立即询问。它可能是这样工作的:

(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 一样查询它以了解它被调用了多少次。

我摸索过函数元数据,但无法想出有效的方法。感觉 defrecorddefprotocol 都可以用,但是在 JavaScript.

中想出这么简单的东西似乎很麻烦

有没有简单的方法来实现这个Clojure/Script?

如果您有一个固定函数,其中包含您想要跟踪的已知数量的参数,您可以创建一个记录并为所需的数量实现 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

如果您同意在您的代码库中使用一些 Java,您可以编写一个扩展 AFn class and wraps an IFn 的 class。在你项目的 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

Metadata comes-in 在这里很方便。

(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也是你可以订阅这个计数器的东西,so-to-speak。

例外情况也是需要考虑的事情。当包裹的 fn 抛出时必须发生什么?