Hacker News new | ask | show | jobs
by mschaef 4533 days ago
I hear this complaint a lot about Clojure, but I have to admit that I don't fully understand it.

My usual experience with Clojure has been that the stack traces are long... but the exception error messages are generally pretty good, and the stack frames that correspond to user code generally have good information on the position of the offending statement.

Maybe you could make an argument that the internal Clojure stack frames should be hidden from stack traces, but Java itself makes that difficult. The mechanisms that `Throwable` uses for capturing and printing the stack back trace are both private methods of the class. Working around this would involve identifying every (most?) places in a codebase where a Clojure stack trace might be printed and then using custom Clojure-specific stack trace printer. This might be doable for stack traces printed by the REPL or compiler, but very difficult for stack traces printed by an external linked-in logging framework.

2 comments

Debuggers can be helpful by disguising/translating details, but I'd be very cautious about a feature that messed with the stack frame. Sometimes that's what's being debugged! It would be a disservice to muddy that information.

Most of us are pretty good at scanning down a stack to find our own stuff.

> My usual experience with Clojure has been that the stack traces are long... but the exception error messages are generally pretty good, and the stack frames that correspond to user code generally have good information on the position of the offending statement.

I do not agree, but please note my comment isn't talking about "running" stack traces here it's talking about the compiler blowing up on what is essentially a syntax error:

    (let [{:keys foo} {:foo 1}]
        (print foo))
note how `foo` is bare rather than in a vector. Put that in a script, `clj -m` it

    Exception in thread "main" java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol, compiling:(hello.clj:5:3)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6567)
        at clojure.lang.Compiler.analyze(Compiler.java:6361)
        at clojure.lang.Compiler.analyze(Compiler.java:6322)
        at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5708)
        at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5139)
        at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3751)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6558)
        at clojure.lang.Compiler.analyze(Compiler.java:6361)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6548)
        at clojure.lang.Compiler.analyze(Compiler.java:6361)
        at clojure.lang.Compiler.access$100(Compiler.java:37)
        at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:529)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6560)
        at clojure.lang.Compiler.analyze(Compiler.java:6361)
        at clojure.lang.Compiler.analyze(Compiler.java:6322)
        at clojure.lang.Compiler.eval(Compiler.java:6623)
        at clojure.lang.Compiler.load(Compiler.java:7064)
        at clojure.lang.RT.loadResourceScript(RT.java:370)
        at clojure.lang.RT.loadResourceScript(RT.java:361)
        at clojure.lang.RT.load(RT.java:440)
        at clojure.lang.RT.load(RT.java:411)
        at clojure.core$load$fn__5018.invoke(core.clj:5530)
        at clojure.core$load.doInvoke(core.clj:5529)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5336)
        at clojure.core$load_lib$fn__4967.invoke(core.clj:5375)
        at clojure.core$load_lib.doInvoke(core.clj:5374)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:619)
        at clojure.core$load_libs.doInvoke(core.clj:5413)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:619)
        at clojure.core$require.doInvoke(core.clj:5496)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.main$main_opt.invoke(main.clj:335)
        at clojure.main$main.doInvoke(main.clj:440)
        at clojure.lang.RestFn.invoke(RestFn.java:436)
        at clojure.lang.Var.invoke(Var.java:423)
        at clojure.lang.AFn.applyToHelper(AFn.java:167)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.main.main(main.java:37)
    Caused by: java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol
        at clojure.lang.RT.seqFrom(RT.java:505)
        at clojure.lang.RT.seq(RT.java:486)
        at clojure.core$seq.invoke(core.clj:133)
        at clojure.core$reduce1.invoke(core.clj:890)
        at clojure.core$destructure$pb__4541$pmap__4544$fn__4547.invoke(core.clj:4013)
        at clojure.core$reduce1.invoke(core.clj:896)
        at clojure.core$destructure$pb__4541$pmap__4544.invoke(core.clj:4014)
        at clojure.core$destructure$pb__4541.invoke(core.clj:4028)
        at clojure.core$destructure$process_entry__4557.invoke(core.clj:4030)
        at clojure.core$reduce1.invoke(core.clj:896)
        at clojure.core$destructure.invoke(core.clj:4033)
        at clojure.core$let.doInvoke(core.clj:4046)
        at clojure.lang.RestFn.invoke(RestFn.java:467)
        at clojure.lang.Var.invoke(Var.java:427)
        at clojure.lang.AFn.applyToHelper(AFn.java:172)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.lang.Compiler.macroexpand1(Compiler.java:6468)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6546)
        ... 40 more
Yeah… the third time around you might instantly know what it is, the first time around when it's a few levels down a type method not so much.
The first line of the error message tells you where and what the error is, almost exactly:

    ... Don't know how to create ISeq from: clojure.lang.Symbol, compiling:(hello.clj:5:3)
It's true that the full interpretation of the error message requires some experience with the language (ISeq? Symbol?), but when is that not the case? Consider this one character typo in a Java file:

    voidx displayMessage(String message);
javac produces an error message that's just about as opaque as what Clojure produces in your example.

    [ERROR] ... MessageSink.java:[5,4] error: cannot find symbol
Conversely, if I leave an open brace in Java source, I get the following kind of error from javac:

    [ERROR] ... ConsoleMessageSink.java:[12,1] error: reached end of file while parsing
If I leave an open form in Clojure, it at least tells me the location of the form I left open:

    Exception in thread "main" java.lang.RuntimeException: EOF while reading, starting at line 18, compiling:(toto/data.clj:255:1)
(Of course, thanks to paredit-mode, I had to play a minor trick on my editor to get it to let me even introduce that error in the first place.)