This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-09-18
Channels
- # announcements (10)
- # babashka (21)
- # beginners (99)
- # biff (66)
- # catalyst (3)
- # cherry (1)
- # cider (11)
- # clojure (38)
- # clojure-austin (3)
- # clojure-dev (23)
- # clojure-europe (21)
- # clojure-hungary (10)
- # clojure-nl (2)
- # clojure-norway (57)
- # clojure-uk (2)
- # clojurescript (9)
- # cursive (6)
- # fulcro (5)
- # hyperfiddle (14)
- # integrant (4)
- # introduce-yourself (3)
- # lsp (24)
- # off-topic (14)
- # reagent (12)
- # reitit (13)
- # releases (8)
- # sci (16)
- # shadow-cljs (8)
- # solo-full-stack (1)
- # spacemacs (5)
- # squint (3)
- # xtdb (14)
is there an existing Ask about the state of error messages? I've been mulling over some suggestions and want to place them in the right spot before opening a whole new ask for it
"the state of error messages" seems very broad, so no, don't think so
my apologies, i have a vague memory of you and sean corfield discussing "error messages" in general in an Ask but can't seem to find it. i'll just open a new one
https://ask.clojure.org/index.php/13044/interest-in-beginner-friendly-variant-of-pst?show=13044#q13044 maybe this?
hmm yes, that was what i was thinking of, thank you
You could also discuss here before making an ask if that’s useful
The root of the issue (around 50% of survey respondents across all editors) seems to be that no one can really articulate specific, actionable problems with particular error messages and/or stack traces. As Alex noted in discussion in that Ask, Clojure itself (in the REPL) hasn't displayed stack traces since 1.10 but other tooling still does. I think the ClassCastException is the most widely reported "problematic" exception these days -- and if Clojure's REPL intercepted and reworded that, and provided that rewording function as an API for editors/tooling to also apply to the error reported to users, we'd see a big improvement in the survey results next time. But I can't substantiate that gut feeling. From now on, I'll try to remember to write down what beginners report here (and on ClojureVerse and Reddit etc) about error messages... If others want to also try to track that, maybe we'll have enough data to provide specific, actionable change suggestions...?
Sure thing. My thought is that the way spec violations are reported makes it hard to know exactly what is wrong and how to fix the problem. Similarly to :pre/:post conditions which lack the msg
part of asserts, nested specs can be hard to know what went wrong.
For example, (ns example (require [clojure.main :as main]))
produces:
((require [clojure.main :as main])) - failed: Extra input spec: :clojure.core.specs.alpha/ns-form
This doesn't help you know what's wrong (especially given that this used to work), just that there's "extra input".
For example, (let [{:a :b} nil])
produces:
:a - failed: simple-symbol? at: [:bindings :form :map-destructure :map-binding 0 :local-symbol] spec: :clojure.core.specs.alpha/local-name
:a - failed: vector? at: [:bindings :form :map-destructure :map-binding 0 :seq-destructure] spec: :clojure.core.specs.alpha/seq-binding-form
:a - failed: map? at: [:bindings :form :map-destructure :map-binding 0 :map-destructure] spec: :clojure.core.specs.alpha/map-bindings
:a - failed: map? at: [:bindings :form :map-destructure :map-binding 0 :map-destructure] spec: :clojure.core.specs.alpha/map-special-binding
:a - failed: qualified-keyword? at: [:bindings :form :map-destructure :qualified-keys-or-syms 0] spec: :clojure.core.specs.alpha/ns-keys
:b - failed: vector? at: [:bindings :form :map-destructure :qualified-keys-or-syms 1] spec: :clojure.core.specs.alpha/ns-keys
:a - failed: #{:as :or :syms :keys :strs} at: [:bindings :form :map-destructure :special-binding 0] spec: :clojure.core.specs.alpha/map-bindings
{:a :b} - failed: simple-symbol? at: [:bindings :form :local-symbol] spec: :clojure.core.specs.alpha/local-name
{:a :b} - failed: vector? at: [:bindings :form :seq-destructure] spec: :clojure.core.specs.alpha/seq-binding-form
Some of these have duplicated predicates: ::core.specs/seq-binding-form
and ::core.specs/ns-keys
, ::core.specs/map-bindings
and ::core.specs/map-special-binding
. It's very hard to parse all of this, tbh. Most of the errors say :a
, but the final two mention the whole map?
For example, (defn bad name [] (+ 1 1))
(intended bad-name
) produces:
name - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
name - failed: (or (nil? %) (sequential? %)) at: [:fn-tail :arity-n :bodies] spec: :clojure.core.specs.alpha/params+body
This mentions name
but having two different failures with nested specs makes it hard to know exactly what needs to change.
For this more simple example, if you don't know the way to define a function by heart, you might read this and think "what does failed: vector?
mean? where is [:fn-tail :arity-1 :params]
? wait, why are there two different competing errors and how do I solve both of them?"
These are similar to things folks new to clojure have asked me when they make mistakes, and while I can tell them how to read the spec errors and infer the solution, it's tough when they ask to see the spec and I have to say "the definition of the spec is far away in a different repo, and also there's no documentation included except for the spec itself, so there's not much to be gained from reading it."
I don't have a great solution at hand, but I've been mulling over ideas such as "custom failure message" fns for specs (a function that takes the explain-data
and produces a clearer/more directed error message to pinpoint the source of the issue), or maybe just alternate ways of displaying the errors themselves that makes the desired data shape more obvious and the deluge of errors less overwhelminggenerically, this is the "fan out" problem where a spec has or/alt behavior (main cases) and the spec errors report all of the possible ways the value it got did not match each of the possible cases. I think we have an old jira on it, but certainly it is in our list of things to improve
Rich has been resistant to the custom spec failure message idea in the past. I think a better rendering for the case of fan-out (same path) is certainly an option, particularly if done in the message formatting in clojure.main (the data has a list of problems, which is quite useful for programmatic processing)
the truth of it is this basically an NFA that is simultaneously trying to match a tree of possible parses and you're getting a list of all the ways it could have succeeded if you had said something different. because we can't divine your intent, it's hard to say whether you meant to destructure a map and passed a bad local form, or if you typo'ed :as, or something even weirder. these problems are sorted longest-path first, because generally whatever you got the farthest in parsing is probably what you most intended
so an alternate approach here would be to just take the first error and format it more prettily, and leave the rest as explorable data somehow
If you don’t mind, what’s the reasoning for disliking custom messages?
Found symbol ':a' when expecting to find a value matching 'simple-symbol?' while parsing clojure.core/let
Path to value: [:bindings :form :map-destructure :map-binding 0 :local-symbol]
For other possible errors, (explain-all)
That’s very similar to what I was thinking
what is potentially most useful is to see the original value being matched against the spec. The tricky part here is that is very common for that value to not match any particular code because it's probably macro-expanded and gensym'ed, and it's a mixture of core, lib, and user code. doing the work to connect this back to the original code in a way that is useful to the user is generally hard, but also very valuable. If I were going to work on one categoric thing, that would be it.
Funnily enough, I'm looking at almost the same problem with HoneySQL (there's an issue for it). If you have a complex DSL expression with an error in it, it's really hard to tie the exception at the point of the error, back to the DSL fragment in a usable way for the user to figure out what went wrong. It is hard.
I think that context loss is the primary reason people generally find Clojure errors to be unsatisfying
what people have come to expect is <listing of their user code>, <pointer to a line/col in that code>, <error>. Our hope for spec was to let you use specs to specify syntax / information structure and derive that kind of thing automatically, and we are definitely not there yet.
Spec is wired into clojure core to be called automatically before macro expansion, so the original code should be available, right? Or is the issue when a macro produces a call to a macro, which hides the original code?
the spec is called for each macro as it expands so the top macro has the original code/data, but anything else may not
Have you tried feeding this spec failure data into https://github.com/bhb/expound or the like? It something that e.g. CIDER could do. I've considered it but personally I don't feel much pain around this area. Speaking of, CIDER (`master`) finally honors Clojure's error phases and will not show a stacktrace for all exceptions that are essentially compile-time ones. https://docs.cider.mx/cider/usage/dealing_with_errors.html#configuration
i've not tried expound but it looks similar to malli's errors which i've used and like. i'd be delighted if the core team adopted that, but i know they're not interested in that direction lol, so my hope is to make the existing errors less opaque while staying within the implicit KISS style of the current error messages