Почему dotrace выдает здесь StackOverflowError? - PullRequest
7 голосов
/ 05 июня 2010
(use '[clojure.contrib.trace])
(dotrace [str] (reduce str [\a \b]))

1 Ответ

9 голосов
/ 05 июня 2010

В двух словах:

Это потому, что 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))

Замены простые и утомительные, и я опускаю их в ответе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...