One of problem, that sometime arise (at least for me) - errors in code
that is performed lazily - stack trace contains data about point where
it's evaluated, not where it's created:
for example, let look to something like:
I have a function that combines several functions
(defn make-funcs [& funcs]
(fn [data]
(flatten (map #(% data) funcs))))
and function that use these data, something like:
(defn do-something [data]
((make-funcs [func1 func2]) data))
if I omit 'funcs' in map call in 'make-funcs', then I'll get following
trace when data will evaluated in do-something, but it will contain no
pointer to make-funcs:
XXXXXXX core [ERROR] - java.lang.RuntimeException:
java.lang.RuntimeException: java.util.concurrent.ExecutionException:
java.lang.IllegalArgumentException: Wrong number of args (1) passed
to: core$map
at clojure.lang.LazySeq.sval (LazySeq.java:47)
clojure.lang.LazySeq.seq (LazySeq.java:63)
clojure.lang.RT.seq (RT.java:450)
clojure.core$seq.invoke (core.clj:122)
clojure.core$dorun.invoke (core.clj:2450)
clojure.core$doall.invoke (core.clj:2465)
my_ns$do_something.invoke (my_ns.clj:189)
Maybe it's possible to keep information about where this lazy seq was created?
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
--
With best wishes, Alex Ott, MBA
http://alexott.net/
Tiwtter: alexott_en (English), alexott (Russian)
Skype: alex.ott
This seems questionable to me. My car will post a 27B/6 error
to the diagnostic interface which tells my mechanic that the
oxygen sensor has failed. For my mechanic that is a good error
message.
However, when my car stalls it would be useless to display 27B/6.
A more useful error message might be "Call a tow truck".
"Good" error messages are extremely context sensitive and are
relative to the mindset of the person. There are times when a
null pointer exception is very precise and times when it is
completely content-free. The null pointer could happen because
there was missing input somewhere else in the program. So even
for a specific programmer the exception might have no meaning.
So my question is: What is the audience for the error message?
In my experience the Symbolics machines had great error messages.
They would throw you into emacs editing the failing source with
the cursor pointed at the specific failing line. You could fix
the failing line and continue from the point of the error. So a
good error message would contain information that would allow
Slime to do the same thing. That would probably be the filename
and source line of the failure.
This could be automated by the compiler. The compiler could see
a class of symbols, such as 'error' or 'blather' or 'opine' and
automatically lay down code to output (file . line) pairs. Slime
could then use this information to open the file@line.
> Most oddball errors are in macroexpansion, so there is no runtime cost
> to doing extra work to improve error messages. These can be
> implemented as pure functions, so even a relative beginner could
> contribute a patch.
>
>
> (3) The clojure.repl/pst macro in 1.3 already provides better control
> of stack trace spewage at the REPL.
>
>
> Please let us know when you get a misleading error message from a
> macroexpansion, so we can make it better. Or contribute a patch along
> the lines of [2].
>
>
> Thanks,
> Stu
>
>
> Stuart Halloway
> Clojure/core
> http://clojure.com
>
>
> [1] http://twitter.com/marick/statuses/33760838540070912
> [2] https://github.com/clojure/clojure/commit/d694d6d45fb46195ae4de01aab9a2b9f9c06355f
>
>
> Please let us know when you get a misleading error message from a macroexpansion, so we can make it better. Or contribute a patch along the lines of [2].
>
What medium is best?
-----
Brian Marick, Artisanal Labrador
Contract programming in Ruby and Clojure
Author of /Ring/ (forthcoming; sample: http://exampler.com/tmp/ring.pdf)
www.exampler.com, www.exampler.com/blog, www.twitter.com/marick
Please let us know when you get a misleading error message from a macroexpansion, so we can make it better. Or contribute a patch along the lines of [2].
What medium is best?
You can make your pre/post condition AssertionErrors more
human-readable by factoring out their logic into helper functions:
(defn valid-article-id? [art-id]
(or (and (> (mod x 42) 3) (frobz x)) (qux x)))
(defn buy-article [art-id]
{:pre [(valid-article-id? art-id)]}
...)
Thus, the AssertionError message will complain about
"valid-article-id?" failing instead of the rather cryptic "or and
mod". If you need even more flexibility, it is also possible to throw
an exception from the helper function. Not pretty, though.
That said, I'd really wish for the private clojure.core/assert-args
macro to be made public. It's very comfortable to work with and
provides for semi-standardized error messages (offending argument name
always in front).
Jeff correct me if I'm wrong but I think you are after something along the following lines.
Instead:
(defn f [x] {:pre [(some-fancy-validation x)]} ..)
To have something like this:
(defn f [x] {:pre [^{:msg "here goes description of what has not been valid"} (some-fancy-validation x)]} ..)
It could turn precondition to a bit more user-friendly construct.
I would also like to have assert-args public.
Cheers,
Hubert.
A while ago Stuart Sierra wrote about using typed assertions in unit
testing. One of his points was to give test functions the ability to
explain why they fail, instead of simply returning false. Rich raised
some interesting arguments contra more exception types, and pro more
value-oriented exceptions:
http://stuartsierra.com/2010/07/19/typed-assertions#comment-43140
>> (defn f [x] {:pre [^{:msg "..."} (some-fancy-validation x)]} ..)
>
> That idea (or some variation) is actually kinda nice.
Personally, I find the suggested syntax a bit verbose. Consider this:
(defn mangle-fullname [firstname lastname]
{:pre [^{:msg "firstname must be a string of 10 to 50 characters"}
(valid-firstname? firstname),
^{:msg "lastname must be a string of 20 to 70 characters"}
(valid-lastname? lastname)]}
(body-that-may-be-shorter-than-above-conditions))
How about integrating these messages into the validator functions?
They are allowed to raise exceptions themselves:
(defn valid-firstname? [firstname]
(if-not (string? firstname)
(throw (AssertionError. "firstname must be a string")))
(if-not (<= 10 (count firstname) 50)
(throw (AssertionError. "firstname must be between 10 to 50
characters long"))
true))
Note the "true" return value, otherwise the precondition will raise an
assertion error itself. Of course, this is still overly verbose, but
at least the verbosity is taken away from mangle-fullname's
definition.
Bummer that 'assert doesn't take an optional failure message, by the
way. 'assert-args does:
(defn mangle-fullname [firstname lastname]
(assert-args mangle-fullname
(string?) firstname "a string for firstname"
(<= 10 (count firstname) 50) "firstname to be between 10 to 50
characters long"
...))
user=> (mangle-fullname "Adam" "West")
IllegalArgumentException: mangle-fullname requires firstname to be
between 10 to 50 characters long
This doesn't allow to factor out the error message into a separate
validator function, though.
Daniel
On the topic of stack traces: it's high time Clojure stopped
generating shit like
java.lang.RuntimeException: java.lang.RuntimeException:
java.lang.IllegalArgumentException: Don't know how to create ISeq
from: java.lang.Integer (NO_SOURCE_FILE:3545)
at clojure.lang.Compiler.eval(Compiler.java:5440)
at clojure.lang.Compiler.eval(Compiler.java:5415)
at clojure.lang.Compiler.eval(Compiler.java:5391)
at clojure.core$eval.invoke(core.clj:2382)
at com.example.yourns$eval39664.invoke(NO_SOURCE_FILE:354)
blah, blah, blah
at java.lang.Thread.run (Thread.java:616)
Caused by: java.lang.RuntimeException:
java.lang.IllegalArgumentException: Don't know how to create ISeq
from: java.lang.Integer
at clojure.core$foo
at clojure.core$bar
at some.package.SomeJavaClass.quux(SomeJavaClass.java:666)
... 17 more
Caused by: java.lang.IllegalArgumentException: Don't know how to
create ISeq from: java.lang.Integer
at clojure.lang.RT.seqFrom(RT.java:471)
at clojure.lang.RT.seq(RT.java:452)
at clojure.lang.RT.cons(RT.java:534)
at clojure.core$cons.invoke(core.clj:28)
... 14 more
without any of the visible stack trace lines containing any reference
to your own project except for
at com.example.yourns$eval39664.invoke(NO_SOURCE_FILE:354)
which just refers to the REPL expression you tried to evaluate which
test exposed the bug, not the line of source containing the bug
itself.
The crucial lines always seems to start one past the end of what the
last stack trace shows, in this case the very next line quite likely
would have been a line of the project trying to cons onto an integer
(which was probably a bad argument it got from its caller, mind).
I suggest altering the default behavior of the REPL in printing stack
traces to compress all but the LAST in a chain of "Caused by:"
exceptions. Usually the last one is the critical one, not the first.
In the meantime I offer this tool:
(defn get-ultimate-cause [e]
(if-let [c (.getCause e)]
(recur c)
e))
(defn p-relevant [e]
(println (.toString e))
(doseq [elt (.getStackTrace e)]
(let [cn (.getClassName elt)]
(if-not
(or
(.startsWith cn "clojure.")
(.startsWith cn "java."))
(println " " (.toString elt))))))
(defmacro ttry [& body]
`(try
(eval (quote (do ~@body)))
(catch Exception e#
(p-relevant (get-ultimate-cause e#)))))
The eval is so it catches both compiler exceptions (e.g. undefined
symbol) and runtime ones (e.g. ClassCastException passing arg of wrong
type somewhere).
It prints just the parts of the stack trace of the final cause
exception you're probably interested in: the ones outside the various
clojure.foo and java.foo namespaces (so, the ones in your own project,
and maybe Swing methods and such, and third-party library entries).
That gets rid of much of the clutter and shows the various lines
within your own code that were involved in the error.
Incidentally, I uncovered a bug in Clojure 1.2(!) while testing this:
(defmacro ttry [& body]
`(try
(eval (quote (do ~@body)))
(catch Exception e#
(let [c# (loop [e# e#]
(if-let [c# (.getCause e#)]
(recur c#)
; First bug site
e#))]
(println (.toString c#))
(doseq [elt# (.getStackTrace c#)]
(let [cn# (.getClassName elt#)]
(if-not
(or
(.startsWith cn# "clojure.")
(.startsWith cn# "java."))
(println " " (.toString elt#)))))))))
; Second bug site
Both compiler exceptions are
java.lang.UnsupportedOperationException: Cannot recur from catch/finally
But in neither location is there an attempt to "recur FROM
catch/finally"; in the first instance, the recur should go back to a
loop entirely contained within the catch, so not out of the catch, and
in the second instance, I can only presume that the doseq expands into
a loop that would also be entirely contained within the catch.
Things like
(loop [x 256 out []]
(if (> x 0)
(try
(let [y (do-something-dangerous-only-when-x-is-100 x)]
(recur (dec x) (conj out y)))
(catch Foo e (recur 42 out)))
out))
are clearly what're supposed to be verboten here -- and even then I'm
not sure why, as this equivalent Java with a continue in a catch is
legal:
List<Bar> out = new ArrayList<Bar>();
for (x = 256; x > 0; x--) {
try {
Bar y = doSomethingDangerousOnlyWhenXIs100(x);
} catch (Foo e) {
x = 42;
continue;
}
out.add(y);
}
return out;
Here's a misleading lack of an error message:
(defn foo [x]
{:pre (odd? x)}
x)
The code may look fine at a glance, but the precondition is not
wrapped in a seq, so the actual preconditions become checks for
truthiness of `odd?` and `x`.
Maybe precondition forms should be required to be vectors, so that
function call forms can't be mistaken for lists of preconditions?
--
Timo
+1
There is an interesting model about error reporting: Clang, one of
C-family languages compiler which uses LLVM.
For example, if you mistake names, Clang searches similar names which
really exist in current environment.
And then Clang illustrates line, column and actual code.
If you want to get more informations, see [1].
Of course, they needs more works and error reporting may slow down.
Also, Clojure has non-preprocessor macro and JVM exception handling,
so we can't reproduce Clang truly.
However, we can learn some from Clang, I think.
Thank you.
[1] http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html
--
Name: OGINO Masanori (荻野 雅紀)
E-mail: masanor...@gmail.com
Here's another error message that really threw me off for a while.
I have some code that looks like this:
(let [[x y] (nth @my-atom z)]
...)
Which occasionally failed with:
java.lang.UnsupportedOperationException: nth not supported on this type: Float
at clojure.lang.RT.nthFrom(RT.java:815) ~[clojure-1.2.0.jar:na]
at clojure.lang.RT.nth(RT.java:765) ~[clojure-1.2.0.jar:na]
I tried to figure out how a single float could have ended up in that
atom and why I couldn't catch it by checking for `(coll? @my-atom)`.
After a while I found out that `@my-atom` indeed contained a
collection -- a collection of floats -- and that the exception was
thrown by the destructuring.
An error message something like this would have been much more helpful:
Can't destructure `(nth @my-atom z)` to `[x y]`: expected a
collection with 2 or more items but got `Float`.
--
Timo
Fixed in master today: http://dev.clojure.org/jira/browse/CLJ-742
Thanks!
Stu
This is interesting. I just checked with my copy of 1.2 and it seems
you can indeed call a symbol as a function. It appears to try to look
itself up in its first argument, the same as a keyword, and if not
found or the first argument is not a map returns the (optional) second
argument:
user=> ('+ {'+ 3 '* 4} 42)
3
user=> ('+ {'- 3 '* 4} 42)
42
Of course this is rather icky to use because of the need to quote the symbols.
So, the arity expected by a function like + ends up irrelevant. If you
invoke something on '+ it wants either one or two arguments.
If you're seeing this error you probably have a buggy macro that's not
unquoting something as many times as it should be. Quoting-depth
errors (other than forgetting to unquote something at all) seem to be
most common when writing macros that emit other macros, and ending up
with nested syntax-quoted expressions, doubled-up ~~@foo type
unquotings, and things like `(quote ~foo) in helper functions.
If the error points to a macro invocation I'd suggest inspecting the
output of (macroexpand-1 '(the-problem-invocation)) and seeing what
you get. If the output contains quoted symbols in operator position
that clearly are meant to be just normal calls to functions then
you've at least determined that the problem is caused by the macro.
Though it could be the case that your arguments to the macro violate
its preconditions. If it's not your own macro, check its
documentation. If it is it has a bug since it's not doing what you
want it to do and it's yours. If it's not it may or may not have a
bug.
Addendum: the most likely macro *argument* error to cause this is
quoting a function name macro argument that you shouldn't be quoting,
e.g. (the-macro 'my-func ...) instead of (the-macro my-func ...). The
macro just plops (quote my-func) wherever it should put my-func and
boom!
Is calling symbols as a function actually used anywhere in Clojure or
in the real world? If not, it might make sense to remove this (rather
unexpected, IMHO) feature as a backwards-incompatible change.
> With a fresh brain (and a fresh cup of coffee), I realized this
> message is probably caused (somehow) by my misuse of the midje
> library. No doubt it does fancy macro stuff under-the-hood.
Indeed it does.
> One could certainly argue that the
> macro should do more to detect syntax errors and report them
> gracefully and I would agree.
I've started work on better error handling. (Monads!) When you figure out what your syntax error was, please report it:
https://github.com/marick/Midje/wiki/Error-message-improvements
(Or report a bug in the tracker if it turns out you were right but Midje was wrong.)
Or (list 'apply '+ 1 1) or `(apply + 1 1), both of which allow you to
put something variable in there, like (list 'apply '+ 1 x) or `(apply
+ 1 ~x).
Note that this will still break at runtime because Integers are not seqable. :-)
You probably want either (apply + [1 1]) or (apply + 1 [1]).
user=> (+ 1 nil)
java.lang.NullPointerException (NO_SOURCE_FILE:0)
Sam
On 8 Feb 2011, at 14:01, Stuart Halloway wrote:
> This conversation began on Twitter [1] but I want to continue it on the mailing list as it may be of broader interest.
>
> The core team is very interested in improving error messages, and relatively less interested in more code to manipulate stack traces, for the following reasons:
>
> (1) The language sits at the bottom, and must solve the problems nobody higher up can solve. Error messages are created at the point of an error, and if the language gets it wrong you may not have the information to fix it later. OTOH, any old library code can reduce a stacktrace.
>
> (2) Better error messages are easy to implement [2]. Most oddball errors are in macroexpansion, so there is no runtime cost to doing extra work to improve error messages. These can be implemented as pure functions, so even a relative beginner could contribute a patch.
>
> (3) The clojure.repl/pst macro in 1.3 already provides better control of stack trace spewage at the REPL.
>
> Please let us know when you get a misleading error message from a macroexpansion, so we can make it better. Or contribute a patch along the lines of [2].
>
> Thanks,
> Stu
>
> Stuart Halloway
> Clojure/core
> [2] https://github.com/clojure/clojure/commit/d694d6d45fb46195ae4de01aab9a2b9f9c06355f