В двух словах:
Это потому, что trace-fn-call
, то есть то, что dotrace
использует для обертывания функций, подлежащих трассировке, использует str
для получения хорошего TRACE foo => val
вывода.
Расширенное объяснение:
Макрос dotrace
делает свое волшебство, устанавливая привязку потока для каждого Var, содержащего функцию для отслеживания; в этом случае есть один такой Вар clojure.core/str
. Замена выглядит примерно так:
(let [f @#'str]
(fn [& args]
(trace-fn-call 'str f args)))
trace-fn-call
, чтобы процитировать его строку документации, "Отслеживает одиночный вызов функции f с аргументами." При этом он вызывает отслеживаемую функцию, принимает к сведению возвращаемое значение, печатает красивое информативное сообщение в форме TRACE foo => val
и возвращает значение, полученное из отслеживаемой функции, чтобы можно было продолжить обычное выполнение.
Как уже упоминалось выше, это сообщение TRACE foo => val
создается с использованием str
; однако в данном случае это фактически отслеживаемая функция, поэтому ее вызов приводит к другому вызову trace-fn-call
, который самостоятельно пытается создать выходную строку трассировки с использованием str
, что приводит к другой вызов trace-fn-call
... в конечном итоге приводит к взрыву стека.
Обходной путь:
Следующие модифицированные версии dotrace
и trace-fn-call
должны работать нормально даже при наличии странных привязок для основных Vars (обратите внимание, что фьючерсы могут не планироваться быстро; если это проблема, см. Ниже):
(defn my-trace-fn-call
"Traces a single call to a function f with args. 'name' is the
symbol name of the function."
[name f args]
(let [id (gensym "t")]
@(future (tracer id (str (trace-indent) (pr-str (cons name args)))))
(let [value (binding [*trace-depth* (inc *trace-depth*)]
(apply f args))]
@(future (tracer id (str (trace-indent) "=> " (pr-str value))))
value)))
(defmacro my-dotrace
"Given a sequence of function identifiers, evaluate the body
expressions in an environment in which the identifiers are bound to
the traced functions. Does not work on inlined functions,
such as clojure.core/+"
[fnames & exprs]
`(binding [~@(interleave fnames
(for [fname fnames]
`(let [f# @(var ~fname)]
(fn [& args#]
(my-trace-fn-call '~fname f# args#)))))]
~@exprs))
(Переплет trace-fn-call
вокруг обычного dotrace
, очевидно, не работает; я думаю, это из-за того, что clojure.*
вызовы Var все еще зашиты компилятором, но это отдельный вопрос. Вышеприведенное сработает Во всяком случае.)
В качестве альтернативы можно использовать указанный выше макрос my-dotrace
вместе с функцией my-trace-fn-call
, не использующей фьючерсы, но измененной для вызова пользовательских замен для функций clojure.contrib.trace
, используя вместо str
следующее:
(defn my-str [& args] (apply (.getRoot #'clojure.core/str) args))
Замены простые и утомительные, и я опускаю их в ответе.