找出Clojure中发生错误的位置

时间:2017-06-06 08:10:24

标签: clojure runtime-error

在大多数情况下,我理解Clojure用它的错误信息告诉我什么。但我仍然无法找出错误发生的地方。

以下是我的意思

的示例
(defn extract [m]
  (keys m))

(defn multiple [xs]
  (map #(* 2 %) xs))

(defn process [xs]
  (-> xs
      (multiple)     ; seq -> seq
      (extract))) ; map -> seq ... fails

(process [1 2 3])

静态类型语言现在告诉我,我试图将序列传递给期望X行上的地图的函数.Clojure以某种方式执行此操作:

ClassCastException java.lang.Long cannot be cast to java.util.Map$Entry

但我仍然不知道错误发生在哪里。显然,对于这个实例来说这很简单,因为只涉及3个函数,你可以轻松地阅读所有这些函数,但随着程序变大,这会很快变老。

有没有办法找出错误发生的地方,而不仅仅是从上到下证明读取代码? (这是我目前的做法)

3 个答案:

答案 0 :(得分:3)

您可以使用clojure.spec。它仍处于alpha状态,并且仍有许多工具支持(希望如此),但仪器功能运行良好。

(ns foo.core
  (:require
   ;; For clojure 1.9.0-alpha16 and higher, it is called spec.alpha
   [clojure.spec.alpha :as s]
   [clojure.spec.test.alpha :as stest]))


;; Extract takes a map and returns a seq
(s/fdef extract
  :args (s/cat :m map?)
  :ret seq?)

(defn extract [m]
  (keys m))


;; multiple takes a coll of numbers and returns a coll of numbers
(s/fdef multiple
  :args (s/cat :xs (s/coll-of number?))
  :ret (s/coll-of number?))

(defn multiple [xs]
  (map #(* 2 %) xs))


(defn process [xs]
  (-> xs
      (multiple)     ; seq -> seq
      (extract))) ; map -> seq ... fails

;; This needs to come after the definition of the specs,
;; but before the call to process.
;; This is something I imagine can be handled automatically
;; by tooling at some point.
(stest/instrument)

;; The println is to force evaluation.
;; If not it wouldn't run because it's lazy and
;; not used for anything.
(println (process [1 2 3]))

运行此文件打印(以及其他信息):

Call to #'foo.core/extract did not conform to spec: In: [0] val: (2
4 6) fails at: [:args :m] predicate: map?  :clojure.spec.alpha/spec
#object[clojure.spec.alpha$regex_spec_impl$reify__1200 0x2b935f0d
"clojure.spec.alpha$regex_spec_impl$reify__1200@2b935f0d"]
:clojure.spec.alpha/value ((2 4 6)) :clojure.spec.alpha/args ((2 4
6)) :clojure.spec.alpha/failure :instrument
:clojure.spec.test.alpha/caller {:file "core.clj", :line 29,
:var-scope foo.core/process}

可以理解为:对exctract的调用失败,因为(2 4 6)中传递的值未通过谓词map?。该调用发生在第29行的"core.clj"文件中。

让人们吵架的一个警告是,仪器只检查函数参数而不返回值。这是Rich Hickey的设计决定(如果你问我的话很奇怪)。有a library for that, though

答案 1 :(得分:0)

如果您有REPL会话,则可以打印堆栈跟踪:

(clojure.stacktrace/print-stack-trace *e 30)

有关打印堆栈跟踪的各种不同方法,请参阅http://puredanger.github.io/tech.puredanger.com/2010/02/17/clojure-stack-trace-repl/。您需要在project.clj

中具有此类依赖关系
[org.clojure/tools.namespace "0.2.11"]

我没有使用上面的方法获得堆栈跟踪,但只需在REPL中键入*e就可以获得有关错误的所有可用信息,说实话这似乎没什么用处。

对于堆栈跟踪无用的极少数情况,我通常使用对函数的调用来调试,该函数返回给出的单个参数,但是具有打印该参数的副作用。我碰巧称这个函数为probe。在您的情况下,它可以放在线程宏中的多个位置。

答案 2 :(得分:0)

重新输入我的例子:

(defn extract [m]
  (keys m))

(defn multiply [xs]
  (mapv #(* 2 %) xs))

(defn process [xs]
  (-> xs
    (multiply)      ; seq -> seq
    (extract)))     ; map -> seq ... fails ***line 21***

(println (process [1 2 3]))
;=> java.lang.ClassCastException: java.lang.Long cannot be cast 
to java.util.Map$Entry, compiling:(tst/clj/core.clj:21:21)

因此,我们在异常中获得了一个很好的线索,其中tst.clj.core.clj:21:21方法存在问题的文件和行/列号extract

我使用的另一个不可或缺的工具是Plumatic Sc​​hema,将“逐渐”类型检查注入clojure。代码变为:

(ns tst.clj.core
  (:use clj.core tupelo.test)
  (:require
    [tupelo.core :as t]
    [tupelo.schema :as tsk]
    [schema.core :as s]))
(t/refer-tupelo)
(t/print-versions)

(s/defn extract :- [s/Any]
  [m :- tsk/Map]
  (keys m))

(s/defn multiply :- [s/Num]
  [xs :- [s/Num]]
  (mapv #(* 2 %) xs))

(s/defn process :- s/Any
  [xs :- [s/Num]]
  (-> xs
    (multiply) ; seq -> seq
    (extract))) ; map -> seq ... fails

(println (process [1 2 3])) 

clojure.lang.ExceptionInfo: Input to extract does not match schema: 
[(named (not (map? [2 4 6])) m)] {:type :schema.core/error, :schema [#schema.core.One{:schema {Any Any}, 
:optional? false, :name m}], 
:value [[2 4 6]], :error [(named (not (map? [2 4 6])) m)]}, 
compiling:(tst/clj/core.clj:23:17)

因此,虽然错误消息的格式有点冗长,但它立即告诉我们将错误类型和/或形状的参数传递给方法extract

请注意,您需要这样一行:

(s/set-fn-validation! true) ; enforce fn schemas

我创建了一个特殊文件test/tst/clj/_bootstrap.clj,因此它总是在同一个地方。

有关Plumatic Sc​​hema的更多信息,请参阅: