Вы можете сохранить количество вызовов в atom
и прикрепить аксессора к функции с помощью with-meta
:
(def sqrt
(let [n (atom 0)]
(with-meta
(fn [x]
(swap! n inc)
(Math/sqrt x))
{::call-count (fn [] @n)})))
Примеры:
((::call-count (meta sqrt))) ;=> 0
(sqrt 0) ;=> 0.0
(sqrt 1) ;=> 1.0
(sqrt 2) ;=> 1.4142135623730951
((::call-count (meta sqrt))) ;=> 3
(sqrt 3) ;=> 1.7320508075688772
(sqrt 4) ;=> 2.0
(sqrt 5) ;=> 2.23606797749979
((::call-count (meta sqrt))) ;=> 6
Это может вызвать значительное замедление в некоторых случаях, но счет всегда будет правильно обновляться, потому что атом Clojure s являются потокобезопасными. Другой подход может заключаться в использовании add-watch
, а не deref
, но который лучше зависит от вашей ситуации. Вы даже можете использовать оба варианта, если хотите.
Вы можете абстрагироваться от детали с defcounted
макросов для определения функций вызова подсчетом и call-count
функцию, чтобы получить количество вызовов функции вызова подсчетом:
(defmacro defcounted [sym params & body]
`(def ~sym
(let [n# (atom 0)]
(with-meta
(fn ~params
(swap! n# inc)
[email protected])
{::call-count (fn [] @n#)}))))
(defn call-count [f]
((::call-count (meta f))))
(defcounted sqrt [x]
(Math/sqrt x))
Примеры:
(call-count sqrt) ;=> 0
(sqrt 0) ;=> 0.0
(sqrt 1) ;=> 1.0
(sqrt 2) ;=> 1.4142135623730951
(call-count sqrt) ;=> 3
(sqrt 3) ;=> 1.7320508075688772
(sqrt 4) ;=> 2.0
(sqrt 5) ;=> 2.23606797749979
(call-count sqrt) ;=> 6
Кроме того, поскольку здесь вы прикрепляете метаданные к самой функции, а не к var, вы можете расширить эту технику и на анонимные функции.
Очевидно, что defcounted
не имеет большого количества функций defn
, поэтому он не является действительно прозрачным для пользователя. При устранении этой проблемы вы можете использовать clojure.spec
, чтобы более легко разобрать аргументы стиля defn
, но я оставлю это для вас, как вам кажется, так как он ортогонален этому вопросу.