Skip to content

Instantly share code, notes, and snippets.

@timothypratley
Last active June 11, 2021 05:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timothypratley/106a67d0356852279515 to your computer and use it in GitHub Desktop.
Save timothypratley/106a67d0356852279515 to your computer and use it in GitHub Desktop.
Error reporting in Clojure are the worst, no error reporting in ClojureScript is the worst.

For Shame!

Clojure error reporting is a problem. This gist contains examples of missed opportunities for a clear error message. Let's discuss how common scenarios can be improved, and get some "patch welcome" tickets out there with clearly defined expectations.

The problem

Helpful error reporting is a solvable problem, one which will benefit a large user base.

The top three Clojure specific questions on Stack Overflow are:

  • "How do you make a web application in Clojure?"
  • "Debugging in Clojure?".
  • "How can I make nrepl-ritz-jack-in work remotely over TRAMP / Emacs" (ritz is a debugging tool)

The top rated Haskell debugging tagged question is ranked 91st.

Great advice about error handling is given in Illuminated Macros.

Clojure

Unused lazy sequence

(do
  (map println (range 10))
  (println "done"))
done

Why not "unused lazy-seq on line n"

Unused expressions

(def r (ref 0))
(dosync alter r inc)
#<core$inc clojure.core$inc@e2a01da>

Why not "unused expression alter", "unused expression r"

Lazy sequence evaluated outside of bindings

(def ^:dynamic *x*)
(println
 (binding [*x* 1]
   (for [i (range 5)]
     (+ i *x*))))
Unhandled java.lang.ClassCastException
   clojure.lang.Var$Unbound cannot be cast to java.lang.Number

Why not tell me that x is unbound?

Lazy sequence evaluated outside of with-open

(println
 (with-open [r (clojure.java.io/reader "/etc/hosts")]
   (map clojure.string/trim (line-seq r))))
Unhandled java.io.IOException
   Stream closed

Why not tell me r is closed? Why not tell me I have a leaking lazy-seq?

Binding errors

(let [baz 1
      [foo bar] baz]
  (+ foo bar))
Caused by java.lang.UnsupportedOperationException
   nth not supported on this type: Long

An accurate error message that could be more helpful: Why not "destructuring baz failed: nth not supported on Long" There is a ticket but the patch is not a good one and the issue is still open.

(let [{:keys [a b]} 1]
  a)
Caused by java.lang.UnsupportedOperationException
   Can't type hint a primitive local

I don't get it.

Assertions

(let [a 1
      b 2]
  (assert (= a b)))
Caused by java.lang.AssertionError
   Assert failed: (= a b)

Yes, I know, that's why I added the assertion, what are a and b please?

Contracts

(defn foo [x]
  {:pre (pos? x)}
  x)
(foo 0)
0

No, this is an error, :pre must be a sequence.

(defn foo2 [x]
  {:pre [(pos? x)]}
  x)
(foo2 0)
Caused by java.lang.AssertionError
   Assert failed: (pos? x)

Yes, but what is x?

Deftest is really def

(use 'clojure.test)
(deftest (is true))
Caused by java.lang.RuntimeException
   First argument to def must be a Symbol

ClojureScript

Undefined function calls

(ftypo 1)
Cannot call method 'call' of undefined

Why not tell me more plainly ftypo is undefined? Why would this code ever compile? When auto-build fails, it should delete the target so I don't think it worked.

Eastwood linter is excellent, but unhelpful when not part of compilation

The Eastwood README has excellent documentation of common problems, and can detect and report them. However it is not part of the compile chain, so is never seen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment