一个函数,该函数创建可以被查询次数的函数

问题描述

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

(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是否有一种简单的实现方法

解决方法

如果您具有要跟踪的已知数量参数的固定函数,则可以创建一条记录并为所需的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