I've written some toy interpreters/compilers in the past, so I associate stacktraces referencing lines in compiler source files with compiler bugs.
If I add the following bad function definition to my compojure project:
(defn dodgy-map []
{:1 :2 :3})
Lein refuses to start:
$ lein ring server-headless
Exception in thread "main" java.lang.RuntimeException: Map literal must contain an even number of forms, compiling:(one_man_wiki/views.clj:19)
at clojure.lang.Compiler.load(Compiler.java:6958)
at clojure.lang.RT.loadResourceScript(RT.java:359)
at clojure.lang.RT.loadResourceScript(RT.java:350)
at clojure.lang.RT.load(RT.java:429)
at clojure.lang.RT.load(RT.java:400)
at clojure.core$load$fn__4890.invoke(core.clj:5415)
at clojure.core$load.doInvoke(core.clj:5414)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5227)
at clojure.core$load_lib.doInvoke(core.clj:5264)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$load_libs.doInvoke(core.clj:5298)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$require.doInvoke(core.clj:5381)
at clojure.lang.RestFn.invoke(RestFn.java:457)
at one_man_wiki.handler$eval1564$loading__4784__auto____1565.invoke(handler.clj:1)
at one_man_wiki.handler$eval1564.invoke(handler.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6501)
at clojure.lang.Compiler.load(Compiler.java:6952)
at clojure.lang.RT.loadResourceScript(RT.java:359)
at clojure.lang.RT.loadResourceScript(RT.java:350)
at clojure.lang.RT.load(RT.java:429)
at clojure.lang.RT.load(RT.java:400)
at clojure.core$load$fn__4890.invoke(core.clj:5415)
at clojure.core$load.doInvoke(core.clj:5414)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5227)
at clojure.core$load_lib.doInvoke(core.clj:5264)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$load_libs.doInvoke(core.clj:5298)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$require.doInvoke(core.clj:5381)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at user$eval1.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6500)
at clojure.lang.Compiler.eval(Compiler.java:6477)
at clojure.core$eval.invoke(core.clj:2797)
at clojure.main$eval_opt.invoke(main.clj:297)
at clojure.main$initialize.invoke(main.clj:316)
at clojure.main$null_opt.invoke(main.clj:349)
at clojure.main$main.doInvoke(main.clj:427)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:419)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
Caused by: java.lang.RuntimeException: Map literal must contain an even number of forms
at clojure.lang.Util.runtimeException(Util.java:170)
at clojure.lang.LispReader$MapReader.invoke(LispReader.java:1071)
at clojure.lang.LispReader.readDelimitedList(LispReader.java:1126)
at clojure.lang.LispReader$ListReader.invoke(LispReader.java:962)
at clojure.lang.LispReader.read(LispReader.java:180)
at clojure.lang.Compiler.load(Compiler.java:6949)
... 51 more
If I reference a variable that doesn't exist:
(defn no-such-variable []
i-dont-exist)
I get an equally ginormous traceback:
Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: i-dont-exist in this context, compiling:(one_man_wiki/views.clj:18)
at clojure.lang.Compiler.analyze(Compiler.java:6281)
at clojure.lang.Compiler.analyze(Compiler.java:6223)
at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5618)
at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5054)
at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3674)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6453)
at clojure.lang.Compiler.analyze(Compiler.java:6262)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6443)
at clojure.lang.Compiler.analyze(Compiler.java:6262)
at clojure.lang.Compiler.access$100(Compiler.java:37)
at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:518)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6455)
at clojure.lang.Compiler.analyze(Compiler.java:6262)
at clojure.lang.Compiler.analyze(Compiler.java:6223)
at clojure.lang.Compiler.eval(Compiler.java:6515)
at clojure.lang.Compiler.load(Compiler.java:6952)
at clojure.lang.RT.loadResourceScript(RT.java:359)
at clojure.lang.RT.loadResourceScript(RT.java:350)
at clojure.lang.RT.load(RT.java:429)
at clojure.lang.RT.load(RT.java:400)
at clojure.core$load$fn__4890.invoke(core.clj:5415)
at clojure.core$load.doInvoke(core.clj:5414)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5227)
at clojure.core$load_lib.doInvoke(core.clj:5264)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$load_libs.doInvoke(core.clj:5298)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$require.doInvoke(core.clj:5381)
at clojure.lang.RestFn.invoke(RestFn.java:457)
at one_man_wiki.handler$eval1564$loading__4784__auto____1565.invoke(handler.clj:1)
at one_man_wiki.handler$eval1564.invoke(handler.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6501)
at clojure.lang.Compiler.load(Compiler.java:6952)
at clojure.lang.RT.loadResourceScript(RT.java:359)
at clojure.lang.RT.loadResourceScript(RT.java:350)
at clojure.lang.RT.load(RT.java:429)
at clojure.lang.RT.load(RT.java:400)
at clojure.core$load$fn__4890.invoke(core.clj:5415)
at clojure.core$load.doInvoke(core.clj:5414)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invoke(core.clj:5227)
at clojure.core$load_lib.doInvoke(core.clj:5264)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$load_libs.doInvoke(core.clj:5298)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invoke(core.clj:603)
at clojure.core$require.doInvoke(core.clj:5381)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at user$eval1.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6500)
at clojure.lang.Compiler.eval(Compiler.java:6477)
at clojure.core$eval.invoke(core.clj:2797)
at clojure.main$eval_opt.invoke(main.clj:297)
at clojure.main$initialize.invoke(main.clj:316)
at clojure.main$null_opt.invoke(main.clj:349)
at clojure.main$main.doInvoke(main.clj:427)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:419)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
Caused by: java.lang.RuntimeException: Unable to resolve symbol: i-dont-exist in this context
at clojure.lang.Util.runtimeException(Util.java:170)
at clojure.lang.Compiler.resolveIn(Compiler.java:6766)
at clojure.lang.Compiler.resolve(Compiler.java:6710)
at clojure.lang.Compiler.analyzeSymbol(Compiler.java:6671)
at clojure.lang.Compiler.analyze(Compiler.java:6244)
... 66 more
Why doesn't the Clojure compiler raise a ClojureSyntaxError
and a ClojureNameError
that could be caught at the top level and a simple error shown? These are common developer errors during development.
If the long tracebacks are useful in some circumstances, why are they truncated?
Edit: What I'm looking for in an answer:
ClojureSyntaxError
as I described above? Is it worth me opening an issue on the Clojure bug tracker? (update: I've opened CLJ-1155)Clojure stacktraces are noisy, and that is a complaint that has apparently been heard. Some librairies provides nicer stacktraces, like clj-stacktrace or the expectations library.
But this is something that is also being improved in Clojure itself, for instance with the recent 1.7 release. See also the clojure.stacktrace api.
So I bet it will only get better over time. Things are very exciting in the Clojure{Script} ecosystem right now.
Replying to your numbered points,
it is idiomatic in Clojure to interoperate directly with Java libraries, so getting the full Java stacktrace can be helpful if you are calling into Java objects in some unexpected or unsupported way.
Sounds like a good idea; I've often at least wished for a settable option which would allow me to see only the parts of stacktraces originating in my own code, suppressing all the underlying language layers.
I usually just do it the hard way and pore over the stacktrace to get the line in my program that barfed, tuning out the clojure.*
parts (and I usually test each minute change so I have a pretty good idea what change caused the problem). Some of the Emacs and Eclipse tools I've used show you only the actual error and not the whole stacktrace; I generally find this to be more helpful.
At Clojure/west in 2012, @chouser gave a nice talk [PDF] on the anatomy of stacktraces, explained how to read them, and introduced a promising-looking tool, which apparently still has not seen the light of day yet.
Compared with, say, Python, whose stacktraces I find pretty user-friendly, I think stacktraces are a rough edge in Clojure, particularly for beginners. This is partly due to the "hosted" nature of the language, though I expect there are improvements which could be made without adding incidental complexity.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With