Two suggestions re: core.spec, `ns`, and clojure 1.9alpha11

1,775 views
Skip to first unread message

Brian Marick

unread,
Aug 20, 2016, 6:17:59 AM8/20/16
to clo...@googlegroups.com
Yesterday, a bug was filed against Suchwow under 1.9alpha11. It turns out to have been a use of `ns …(require…` instead of `(ns …(:require`. Not in Suchwow, but in Midje. Unfortunately, the Suchwow file the bug report pointed at *also* had that typo - apparently I am prone to it - so adding the colon to the require there didn’t make the problem go away. 

That caused me to lose my temper and make a fool of myself, which is neither here nor there, except that I apologize to @puredanger. 

I have two suggestions, though:

1. It has long been the case that Clojure allowed `(ns (require…)` even though that’s strictly incorrect. I suggest that, for backwards compatibility, it be allowed going forward. That is, I think it does no harm for a correct `ns` statement to allow symbols as well as keywords. That wrong code in Midje has been there since Clojure 1.2. 

2. The following is not a good error message:

Exception in thread "main" java.lang.IllegalArgumentException: Call to clojure.core/ns did not conform to spec:
In: [2] val: ((require [such.vars :as var] [such.immigration :as immigrate]) (require midje.checking.checkers.defining midje.checking.checkers.chatty midje.checking.checkers.simple midje.checking.checkers.combining midje.checking.checkers.collection)) fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input

- It would be better to say “`require` should be a keyword, not a symbol” than "fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input”

- Suggest Clojure.spec error messages follow the “inverted pyramid” structure of news reports: https://en.wikipedia.org/wiki/Inverted_pyramid That would mean the text message about the error would come first, the spec second, and the wrong expression last. 

- It would be better to name the namespace that has the problem.

- The stack trace adds nothing to the error. If anything, it makes it less understandable, as the sheer amount of text is offputting.

My https://github.com/marick/structural-typing does (a small) part of what clojure.spec does. I went to a lot of effort to get it to produce good error messages, and it turned out OK. I doubt it would be applicable to clojure.spec, but I’d be happy to sign over any code that could be of use. 

It’s unfortunate that reporting errors is so much harder than detecting them. 

Alex Miller

unread,
Aug 20, 2016, 10:03:51 AM8/20/16
to Clojure


On Saturday, August 20, 2016 at 5:17:59 AM UTC-5, Brian Marick wrote:
Yesterday, a bug was filed against Suchwow under 1.9alpha11. It turns out to have been a use of `ns …(require…` instead of `(ns …(:require`. Not in Suchwow, but in Midje. Unfortunately, the Suchwow file the bug report pointed at *also* had that typo - apparently I am prone to it - so adding the colon to the require there didn’t make the problem go away. 

That caused me to lose my temper and make a fool of myself, which is neither here nor there, except that I apologize to @puredanger. 

I have two suggestions, though:

1. It has long been the case that Clojure allowed `(ns (require…)` even though that’s strictly incorrect. I suggest that, for backwards compatibility, it be allowed going forward. That is, I think it does no harm for a correct `ns` statement to allow symbols as well as keywords. That wrong code in Midje has been there since Clojure 1.2. 

We discussed this before releasing the specs and decided to start on the strict side. That said, this is still an alpha and there is plenty of time to change our minds prior to official release of 1.9 if that ends up being a catastrophic decision.
 

2. The following is not a good error message:

Exception in thread "main" java.lang.IllegalArgumentException: Call to clojure.core/ns did not conform to spec:
In: [2] val: ((require [such.vars :as var] [such.immigration :as immigrate]) (require midje.checking.checkers.defining midje.checking.checkers.chatty midje.checking.checkers.simple midje.checking.checkers.combining midje.checking.checkers.collection)) fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input

You left out this next important line too since it points you to exactly the file and line where the error occurs:

, compiling:(such/sequences.clj:1:1) 

spec produces very detailed error messages driven by the specs and the value being validated. I admit that in some cases the output from a spec error (particularly for complicated syntaxes where there are wide alternative fan-outs) is daunting. However, spec error messages are going to be increasingly common for all of us to see and understand and I think it is worth taking the time to slow down and actually read them.

> Call to clojure.core/ns did not conform to spec:
              ^^^^^^^^^^^^^^ <- macro that was passed invalid values 
> In: [2] 
        ^^^ <- the data path in the :args passed to the macro, here, the 2th element is the require clause (ns = 0, such.sequences = 1)

> val: ((require [such.vars :as var] ...)
         ^^ <- the remaining part of the value that did not match (it has already matched or "consumed" the first two elements successfully)

> fails at: [:args]
               ^^^^^^ <- the path in the ns fdef spec to the failure point

> predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  
                    ^^^etc -> the remaining part of the spec it was trying to match

> Extra input
  specs way of saying that it found something (the val above) but it wasn't what the spec expected next


I'm not trying to pretend this is as easy to digest as an error message that would be produced by hand-written validation and error code, but it's also notoriously difficult to cover all possible cases (which is why the Clojure DSLs have so many error gaps despite having a lot of that code). We are looking to decrease the amount of custom error detection and reporting, so anything we do has to be something we can do generically.

For the specific case of macroexpanded error reporting, I think there *are* more things we can do here (generically) that will improve readability. We *know* we are in the context of checking an fdef spec on a macro, so some of the ":args" stuff is maybe not necessary. Having the val and predicate for a s/cat forwarded to the point of mismatch is both great (as it's specific) but also confusing (because parts of both the input and the cat spec) are missing which removes important context. I think there are ways to indicate that's happening better. Earlier versions of this also reported the full args and we ended up removing that because in many cases it feels redundant (or is potentially large). Maybe there is some heuristic we could follow on when that would help. 
 
- It would be better to say “`require` should be a keyword, not a symbol” than "fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input”

- Suggest Clojure.spec error messages follow the “inverted pyramid” structure of news reports: https://en.wikipedia.org/wiki/Inverted_pyramid That would mean the text message about the error would come first, the spec second, and the wrong expression last. 

- It would be better to name the namespace that has the problem.

As mentioned above, the next line that you omitted points to the file and line with the problem (I went to some trouble to ensure that was reported properly from the point of use).
 
- The stack trace adds nothing to the error. If anything, it makes it less understandable, as the sheer amount of text is offputting.

I agree, but this is to some degree an orthogonal issue. Tools (including the base REPL) choose what to do when they receive an exception from the compiler.  I would like to think more carefully about what the compiler is producing (informationally) and also how tools should be expected to handle these errors. Certainly in cases like this, the stack trace is unnecessary and that's true of many compiler errors. That is a fixable problem and I am interested in making improvements in this area still in 1.9.
 
My https://github.com/marick/structural-typing does (a small) part of what clojure.spec does. I went to a lot of effort to get it to produce good error messages, and it turned out OK. I doubt it would be applicable to clojure.spec, but I’d be happy to sign over any code that could be of use. 

Thanks, I doubt there's much we could use directly.
 
It’s unfortunate that reporting errors is so much harder than detecting them. 

Indeed. Unfortunately errors are read by people and people are hard. :) 

Brian Marick

unread,
Aug 20, 2016, 10:40:21 AM8/20/16
to clo...@googlegroups.com

On Aug 20, 2016, at 9:03 AM, Alex Miller <al...@puredanger.com> wrote:

You left out this next important line too since it points you to exactly the file and line where the error occurs:

, compiling:(such/sequences.clj:1:1) 

This is interesting. Here’s why I missed it. I attach the error message I saw from `lein midje`. Notice that the spec error appears twice, once at the top, once at the end. The line showing the source file appears only with the top one. The one at the bottom of the screen is the one I looked at. I think that’s pretty natural.

==== attach

Exception in thread "main" java.lang.IllegalArgumentException: Call to clojure.core/ns did not conform to spec:
In: [2] val: ((require [such.vars :as var] [such.immigration :as immigrate]) (require midje.checking.checkers.defining midje.checking.checkers.chatty midje.checking.checkers.simple midje.checking.checkers.combining midje.checking.checkers.collection)) fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input
:clojure.spec/args  (midje.checkers "Checkers are for checking results of checkables, or checking \n   that appropriate arguments are passed to prerequisites" (require [such.vars :as var] [such.immigration :as immigrate]) (require midje.checking.checkers.defining midje.checking.checkers.chatty midje.checking.checkers.simple midje.checking.checkers.combining midje.checking.checkers.collection))
, compiling:(midje/checkers.clj:1:1)
at clojure.lang.Compiler.macroexpand1(Compiler.java:6795)
at clojure.lang.Compiler.macroexpand(Compiler.java:6861)
at clojure.lang.Compiler.eval(Compiler.java:6935)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:512)
at midje.parsing.3_from_lexical_maps.from_fake_maps$eval8453$loading__7531__auto____8454.invoke(from_fake_maps.clj:1)
at midje.parsing.3_from_lexical_maps.from_fake_maps$eval8453.invokeStatic(from_fake_maps.clj:1)
at midje.parsing.3_from_lexical_maps.from_fake_maps$eval8453.invoke(from_fake_maps.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:551)
at midje.parsing.lexical_maps$eval8445$loading__7531__auto____8446.invoke(lexical_maps.clj:1)
at midje.parsing.lexical_maps$eval8445.invokeStatic(lexical_maps.clj:1)
at midje.parsing.lexical_maps$eval8445.invoke(lexical_maps.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:512)
at midje.parsing.2_to_lexical_maps.fakes$eval8437$loading__7531__auto____8438.invoke(fakes.clj:1)
at midje.parsing.2_to_lexical_maps.fakes$eval8437.invokeStatic(fakes.clj:1)
at midje.parsing.2_to_lexical_maps.fakes$eval8437.invoke(fakes.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:1523)
at midje.data.prerequisite_state$eval8244$loading__7531__auto____8245.invoke(prerequisite_state.clj:1)
at midje.data.prerequisite_state$eval8244.invokeStatic(prerequisite_state.clj:1)
at midje.data.prerequisite_state$eval8244.invoke(prerequisite_state.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:805)
at midje.checking.checkables$eval8208$loading__7531__auto____8209.invoke(checkables.clj:1)
at midje.checking.checkables$eval8208.invokeStatic(checkables.clj:1)
at midje.checking.checkables$eval8208.invoke(checkables.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:436)
at midje.sweet$eval7876$loading__7531__auto____7877.invoke(sweet.clj:4)
at midje.sweet$eval7876.invokeStatic(sweet.clj:4)
at midje.sweet$eval7876.invoke(sweet.clj:4)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:2793)
at midje.repl$eval4497$loading__7531__auto____4498.invoke(repl.clj:1)
at midje.repl$eval4497.invokeStatic(repl.clj:1)
at midje.repl$eval4497.invoke(repl.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.RT.loadResourceScript(RT.java:374)
at clojure.lang.RT.loadResourceScript(RT.java:365)
at clojure.lang.RT.load(RT.java:455)
at clojure.lang.RT.load(RT.java:421)
at clojure.core$load$fn__7645.invoke(core.clj:6008)
at clojure.core$load.invokeStatic(core.clj:6007)
at clojure.core$load.doInvoke(core.clj:5991)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at clojure.core$load_one.invokeStatic(core.clj:5812)
at clojure.core$load_one.invoke(core.clj:5807)
at clojure.core$load_lib$fn__7590.invoke(core.clj:5852)
at clojure.core$load_lib.invokeStatic(core.clj:5851)
at clojure.core$load_lib.doInvoke(core.clj:5832)
at clojure.lang.RestFn.applyTo(RestFn.java:142)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$load_libs.invokeStatic(core.clj:5889)
at clojure.core$load_libs.doInvoke(core.clj:5873)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$require.invokeStatic(core.clj:5911)
at clojure.core$require.doInvoke(core.clj:5911)
at clojure.lang.RestFn.invoke(RestFn.java:408)
at user$eval4491.invokeStatic(form-init1525926663091106112.clj:1)
at user$eval4491.invoke(form-init1525926663091106112.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6951)
at clojure.lang.Compiler.eval(Compiler.java:6941)
at clojure.lang.Compiler.eval(Compiler.java:6940)
at clojure.lang.Compiler.load(Compiler.java:7403)
at clojure.lang.Compiler.loadFile(Compiler.java:7341)
at clojure.main$load_script.invokeStatic(main.clj:276)
at clojure.main$init_opt.invokeStatic(main.clj:278)
at clojure.main$init_opt.invoke(main.clj:278)
at clojure.main$initialize.invokeStatic(main.clj:309)
at clojure.main$null_opt.invokeStatic(main.clj:343)
at clojure.main$null_opt.invoke(main.clj:340)
at clojure.main$main.invokeStatic(main.clj:422)
at clojure.main$main.doInvoke(main.clj:385)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
Caused by: java.lang.IllegalArgumentException: Call to clojure.core/ns did not conform to spec:
In: [2] val: ((require [such.vars :as var] [such.immigration :as immigrate]) (require midje.checking.checkers.defining midje.checking.checkers.chatty midje.checking.checkers.simple midje.checking.checkers.combining midje.checking.checkers.collection)) fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input
:clojure.spec/args  (midje.checkers "Checkers are for checking results of checkables, or checking \n   that appropriate arguments are passed to prerequisites" (require [such.vars :as var] [such.immigration :as immigrate]) (require midje.checking.checkers.defining midje.checking.checkers.chatty midje.checking.checkers.simple midje.checking.checkers.combining midje.checking.checkers.collection))

at clojure.spec$macroexpand_check.invokeStatic(spec.clj:627)
at clojure.spec$macroexpand_check.invoke(spec.clj:616)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.AFn.applyTo(AFn.java:144)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.lang.Compiler.macroexpand1(Compiler.java:6789)
... 239 more
Error encountered performing task 'midje' with profile(s): 'base,system,user,provided,dev,1.9'
Subprocess failed
986 $ 

Brian Marick

unread,
Aug 20, 2016, 10:58:14 AM8/20/16
to clo...@googlegroups.com
On Aug 20, 2016, at 9:03 AM, Alex Miller <al...@puredanger.com> wrote:

We discussed this before releasing the specs and decided to start on the strict side. That said, this is still an alpha and there is plenty of time to change our minds prior to official release of 1.9 if that ends up being a catastrophic decision.

I urge you to change your minds. Whose life is harmed, and how, by continuing to allow `require` as well as `:require`? 

Alex Miller

unread,
Aug 20, 2016, 3:12:00 PM8/20/16
to Clojure


On Saturday, August 20, 2016 at 9:40:21 AM UTC-5, Brian Marick wrote:

On Aug 20, 2016, at 9:03 AM, Alex Miller <al...@puredanger.com> wrote:

You left out this next important line too since it points you to exactly the file and line where the error occurs:

, compiling:(such/sequences.clj:1:1) 

This is interesting. Here’s why I missed it. I attach the error message I saw from `lein midje`. Notice that the spec error appears twice, once at the top, once at the end. The line showing the source file appears only with the top one. The one at the bottom of the screen is the one I looked at. I think that’s pretty natural.

This is what I'm talking about as far as how tools (incl the REPL) display compiler errors, so in agreement.

Alex Miller

unread,
Aug 20, 2016, 3:13:38 PM8/20/16
to Clojure
The downside is that we then have to support two similar but different ways to do the same thing forever and that adds complexity.

But your feedback is noted and we'll see how things progress before we make any decisions.


se...@corfield.org

unread,
Aug 20, 2016, 6:26:48 PM8/20/16
to clo...@googlegroups.com

I disagree (strongly) with your position here Brian. I’ll try to explain clearly why but first a little background…

 

At World Singles, we’ve always done multi-version testing against the stable version of Clojure that we plan to use in production and also against the very latest master SNAPSHOT. This gives us an early heads up when a breaking change is introduced. When the ns spec hit master, our build broke. We had three namespaces with incorrect syntax – based on the documentation and what all the books and tutorials show – so we simply fixed them (one was require, two were import). Luckily none of the libraries we rely on had similar breakages.

 

Then we moved to Alpha 11 and found another namespace with require – clearly our tests don’t have sufficient coverage (duly noted, JIRA issue to follow I expect).

 

Despite having to fix our code, we welcome the stricter compiler checking here. There are very few things in language design that frustrate me more than preserving bad compiler behavior in the name of “backward compatibility” on the grounds that “if we fix this, people who have code that we never intended to work will see breakages, and be forced to correct their bad code”. That’s what you’re asking for here: that Clojure/core preserve unintended behavior so that people who have code that works “accidentally” aren’t forced to modify their code to match what has always been intended.

 

Why do I feel so strongly about this? A few things… I built one of the first ANSI-validated C compilers which focused on “the letter of the law” as far as flagging undefined, unspecified, and implementation-defined behavior. After that, I was on the ANSI C++ Standards Committee for eight years where we had to deal with this same sort of issue over and over again in terms of deciding what unintended legacy behaviors should be enshrined as standard vs outlawed (vs pushed to one of those three behaviors). After all that standards work, I then had to deal with Macromedia / Adobe ColdFusion on and off since 2001: a product that values backward compatibility so deeply that it repeatedly does exactly what you’re asking Clojure/core to do – it won’t fix any number of bugs because they might break unintentionally working code. You can’t begin to imagine what decades of that position does to a language – it’s a horrible, inconsistent, mess of a language, full of special cases, warts, and surprising behavior. I wouldn’t wish that on any sane developer.

 

Sean Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org

--
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
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

 

Timothy Baldridge

unread,
Aug 20, 2016, 6:43:41 PM8/20/16
to clo...@googlegroups.com
As a side note to this conversation, I hit this (require) vs (:require) thing almost a year ago when porting a file to CLJS. ClojureScript has been enforcing these keyword regulations for some time, as well as only allowing a single :require, not allowing anything but :require, :use, etc.


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.

To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.


For more options, visit https://groups.google.com/d/optout.

 

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Brian Marick

unread,
Aug 20, 2016, 6:49:02 PM8/20/16
to clo...@googlegroups.com

On Aug 20, 2016, at 5:26 PM, se...@corfield.org wrote:

I disagree (strongly) with your position here Brian. I’ll try to explain clearly why but first a little background…

I too have felt the pain of having to maintain backward compatibility. However, I’m reminded, in this case, of Mark Twain’s “The cat, having sat upon a hot stove lid, will not sit upon a hot stove lid again. But he won't sit upon a cold stove lid, either.”

That’s why I tend to ask questions, not about abstract principles, but about what - given these particular alternatives - happens to which people?

The problem with recourse to general principles and past experience is that it’s easy to overlook that I’m suggesting a change from:

  ns-clause-header ::= keyword?

… to:

  ns-clause-header ::= keyword? || symbol?


Timothy Baldridge

unread,
Aug 20, 2016, 7:30:59 PM8/20/16
to clo...@googlegroups.com
Brian, let's make it more concrete then...why should the Clojure compiler continue to support undocumented features that make code unportable?



--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Brian Marick

unread,
Aug 20, 2016, 9:22:58 PM8/20/16
to clo...@googlegroups.com

On Aug 20, 2016, at 6:30 PM, Timothy Baldridge <tbald...@gmail.com> wrote:

Brian, let's make it more concrete then...why should the Clojure compiler continue to support undocumented features that make code unportable?

Because: 

1. People who want to port to clojurescript will incur exactly the same cost as they do now.
2. People who don’t want to port to clojurescript will incur no additional cost.
3. Clojurescript maintainers will incur no additional cost.
4. Clojure maintainers will incur the cost of adding “or symbol” to current code.
5. No one writing documentation will incur any cost, as what was not mentioned before will continue to be unmentioned.

6. There will be a psychic cost because of an undocumented inconsistency between clojure and clojurescript.
7. If, at some point, clojure and clojurescript shared code for the implementation of `ns`, one or the other would have to change the pre 1.9-alpha11 behavior.

Do I have this enumeration of costs wrong?

It’s a bit surprising to me that my explicit appeal to consider costs and benefits to real people is not being addressed.

Colin Fleming

unread,
Aug 20, 2016, 10:14:01 PM8/20/16
to clo...@googlegroups.com
With respect to preserving undocumented behaviour, while in general I'm in favour of making compilers stricter, in this case it seems like the change breaks a lot of existing code in ways that are impossible for library consumers to fix themselves - they have to wait for an update to the library, or fork it. Leaving the symbol option seems like a very low-impact change, it's not going to be a massive amount of technical debt in Clojure itself. There are many areas of unspecified behaviour in the reader (for example, keywords starting with numbers, the keyword function allowing creation of unreadable keywords etc) which have not been fixed because it would break some existing code - I suspect the impact of fixing that would be far less than the impact of this change. 

I don't understand why this particular change is so important that significant breakage to real code is considered acceptable. I agree with Brian that it doesn't seem very pragmatic. 

--

Colin Fleming

unread,
Aug 20, 2016, 10:27:36 PM8/20/16
to clo...@googlegroups.com
I think there's considerable scope to produce better error messages automatically than what spec produces, and I hope that can happen for 1.9. The error message produced by the code I demoed at the conj last year would be:

Unexpected symbol 'require' at <exact error location> while parsing namespace clauses. Expected :refer-clojure, :require, :use, :import, :load or :gen-class. 

The only thing the grammar developer needs to do to get that error is to provide a descriptive name of "namespace clauses" for the main alternation. If they don't do that, the error message would just say "...while parsing clojure.core/ns..." instead, which is still acceptable.

There is plenty of prior art on how to do this, it's not a "people are hard" problem. I think the precise error messages you want for validating data structures are not optimal for code error messages. I know the mantra is that code is data, but in this case I believe that a specialised implementation for code is superior. But I realise that ship has sailed.

--

Andrew Oberstar

unread,
Aug 20, 2016, 10:38:26 PM8/20/16
to clo...@googlegroups.com
What about a compromise where you could opt-in or opt-out of checking macro specs at compile time (via a compiler option)? It seems worth preserving the correctness of the spec, without forcing all of the breakage.

Andrew Oberstar

On Sat, Aug 20, 2016 at 9:13 PM Colin Fleming <colin.ma...@gmail.com> wrote:
With respect to preserving undocumented behaviour, while in general I'm in favour of making compilers stricter, in this case it seems like the change breaks a lot of existing code in ways that are impossible for library consumers to fix themselves - they have to wait for an update to the library, or fork it. Leaving the symbol option seems like a very low-impact change, it's not going to be a massive amount of technical debt in Clojure itself. There are many areas of unspecified behaviour in the reader (for example, keywords starting with numbers, the keyword function allowing creation of unreadable keywords etc) which have not been fixed because it would break some existing code - I suspect the impact of fixing that would be far less than the impact of this change. 

I don't understand why this particular change is so important that significant breakage to real code is considered acceptable. I agree with Brian that it doesn't seem very pragmatic. 
On 21 August 2016 at 13:22, Brian Marick <mar...@roundingpegs.com> wrote:

On Aug 20, 2016, at 6:30 PM, Timothy Baldridge <tbald...@gmail.com> wrote:

Brian, let's make it more concrete then...why should the Clojure compiler continue to support undocumented features that make code unportable?

Because: 

1. People who want to port to clojurescript will incur exactly the same cost as they do now.
2. People who don’t want to port to clojurescript will incur no additional cost.
3. Clojurescript maintainers will incur no additional cost.
4. Clojure maintainers will incur the cost of adding “or symbol” to current code.
5. No one writing documentation will incur any cost, as what was not mentioned before will continue to be unmentioned.

6. There will be a psychic cost because of an undocumented inconsistency between clojure and clojurescript.
7. If, at some point, clojure and clojurescript shared code for the implementation of `ns`, one or the other would have to change the pre 1.9-alpha11 behavior.

Do I have this enumeration of costs wrong?

It’s a bit surprising to me that my explicit appeal to consider costs and benefits to real people is not being addressed.

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Sean Corfield

unread,
Aug 20, 2016, 10:41:43 PM8/20/16
to Clojure Mailing List
On 8/20/16, 7:13 PM, "Colin Fleming" <clo...@googlegroups.com on behalf of colin.ma...@gmail.com> wrote:
> in this case it seems like the change breaks a lot of existing code

I disagree. Compared to the vast amount of Clojure code out there, I would contend that this breaks very little code – and that code is arguably wrong in the first place. Most of the handful of library maintainers that have been notified about this increase in strictness have been quick to fix their code (and mostly have been quick to release new versions). This has also been my experience so far for libraries that defined their own versions of one or more of the new predicates added in Clojure 1.9.0 – very quick updates to add the appropriate :exclude to :refer-clojure in those namespaces (and that was for a _warning_, not even an error!).

> they have to wait for an update to the library, or fork it.

Or stay on Clojure 1.8.0. Which is true of any other change in Clojure itself that causes breakage in code.

I find it very interesting that, in the past we’ve often see relatively slow take up of the prerelease builds, with folks saying they don’t want to use prerelease software, yet for Clojure 1.9.0 we’re seeing much more uptake of clojure.spec driving early adoption of these builds.

Sean



Sean Corfield

unread,
Aug 20, 2016, 10:51:13 PM8/20/16
to Clojure Mailing List
Or keep the stricter compiler and:

1. People who want to port to clojurescript will incur exactly the same cost as they do now.

**2. People who don’t want to port to clojurescript and don’t want to move to Clojure 1.9 will incur no additional cost.

3. Clojurescript maintainers will incur no additional cost.

**4. COST REMOVED: Clojure maintainers will incur NO additional cost.

5. No one writing documentation will incur any cost, as what was not mentioned before will continue to be unmentioned.

**6. COST REMOVED: There will be NO psychic cost because there will NOT be an undocumented inconsistency between clojure and clojurescript.

**7. COST REMOVED: If, at some point, clojure and clojurescript shared code for the implementation of `ns`, neither of them would have to change how they interpret the ns form

**8. BENEFIT ADDED: We benefit because an undocumented and unintended behavior went away, and one of the most confusing and complex Clojure forms becomes more consistent – we no longer have to explain to beginners that trip over (require …) mysteriously working inside ns, contrary to all the documentation out there, that “Yeah, we know (require …) works in ns, like it does outside ns, but it’s not supposed to and you shouldn’t do that – it’s a bug in Clojure”…



John Newman

unread,
Aug 20, 2016, 11:16:48 PM8/20/16
to Clojure Mailing List

I'd prefer getting rid of the symbol option. Some kind of deprecation warning for a version or two might be an idea though.


--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Christopher Small

unread,
Aug 21, 2016, 1:19:08 AM8/21/16
to Clojure
I couldn't help myself...



Alex Miller

unread,
Aug 21, 2016, 4:08:58 AM8/21/16
to Clojure
The documentation now includes the spec, which would explicilly mention the symbol, so this would not be tacitly hidden as you suggest. David is already working on porting these specs to ClojureScript so that issue is one we will imminently face.

So again I will state: while the current spec does not support the symbol version we are in an investigatory period and things may change before release.

Brian Marick

unread,
Aug 21, 2016, 6:25:03 PM8/21/16
to clo...@googlegroups.com
As an update. I’ve fixed the `ns` oopsie in Suchwow (one file), and the coincident `ns` oopsie in Midje (one file). But this happens when running Midje’s self-tests against Clojure 1.9alpha11:

> Exception in thread "main" java.lang.IllegalArgumentException: Call to clojure.core/fn did not conform to spec:
> In: [0] val: clojure.core.unify/var-unify fails spec: :clojure.core.specs/arg-list at: [:args :bs :arity-1 :args] predicate: vector?
> In: [0] val: clojure.core.unify/var-unify fails spec: :clojure.core.specs/args+body at: [:args :bs :arity-n] predicate: (cat :args :clojure.core.specs/arg-list :prepost (? map?) :body (* any?))
> :clojure.spec/args (clojure.core.unify/var-unify [varp v expr binds] (clojure.core/if-let [vb__10124__auto__ (binds v)] (clojure.core.unify/garner-unifiers varp vb__10124__auto__ expr binds) (clojure.core/if-let [vexpr__10125__auto__ (clojure.core/and (varp expr) (binds expr))] (clojure.core.unify/garner-unifiers varp v vexpr__10125__auto__ binds) (if (clojure.core.unify/occurs? varp v expr binds) (throw (java.lang.IllegalStateException. (clojure.core/str "Cycle found in the path " expr))) (clojure.core.unify/bind-phase binds v expr)))))
> , compiling:(clojure/core/unify.clj:82:18)

I suspect the problem is that Midje uses an old version of clojure.core.unify (0.5.2). I use that old version because a later versions (like the current 0.5.7) failed in my use case for no reason I could quickly understand. So I decided to just stick with the older, working version because forced upgrades make kittens suffer. And I am a kitten. And also: by semver, why should a satisfied user of 0.5.2 care about 0.5.7?

(Note: I don’t know whether clojure.core.unify 0.5.7 is conformant to whatever spec is breaking.)

So upgrading Midje to Clojure 1.0alpha11 is not turning out as simple as might be hoped.

I know that, pace Casablanca[*], the problems of a guy trying to provide an alternative to clojure.test don’t amount to a hill of beans in this crazy world. But I offer this datapoint for clojure.core.team consideration.

————

[*]

Rick: But I've got a job to do, too. Where I'm going, you can't follow. What I've got to do, you can't be any part of. Ilsa, I'm no good at being noble, but it doesn't take much to see that the problems of three little people don't amount to a hill of beans in this crazy world. Someday you'll understand that.
[Ilsa lowers her head and begins to cry]
Rick: Now, now...
[Rick gently places his hand under her chin and raises it so their eyes meet]
Rick: Here's looking at you kid.

lvh

unread,
Aug 21, 2016, 6:28:57 PM8/21/16
to clo...@googlegroups.com
FYI, while I disagree with your conclusion (I think we should go fix libraries instead), I ran into the same issue just now for roughly the same reason, except the thing that pulled in an old version of core.unify was core.typed, which pulls in 0.5.3 through core.contracts.
> --
> 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
> ---
> You received this message because you are subscribed to the Google Groups "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Alex Miller

unread,
Aug 21, 2016, 9:23:03 PM8/21/16
to Clojure


On Sunday, August 21, 2016 at 5:25:03 PM UTC-5, Brian Marick wrote:
As an update. I’ve fixed the `ns` oopsie in Suchwow (one file), and the coincident `ns` oopsie in Midje (one file). But this happens when running Midje’s self-tests against Clojure 1.9alpha11:

> Exception in thread "main" java.lang.IllegalArgumentException: Call to clojure.core/fn did not conform to spec:
> In: [0] val: clojure.core.unify/var-unify fails spec: :clojure.core.specs/arg-list at: [:args :bs :arity-1 :args] predicate: vector?
> In: [0] val: clojure.core.unify/var-unify fails spec: :clojure.core.specs/args+body at: [:args :bs :arity-n] predicate: (cat :args :clojure.core.specs/arg-list :prepost (? map?) :body (* any?))
> :clojure.spec/args  (clojure.core.unify/var-unify [varp v expr binds] (clojure.core/if-let [vb__10124__auto__ (binds v)] (clojure.core.unify/garner-unifiers varp vb__10124__auto__ expr binds) (clojure.core/if-let [vexpr__10125__auto__ (clojure.core/and (varp expr) (binds expr))] (clojure.core.unify/garner-unifiers varp v vexpr__10125__auto__ binds) (if (clojure.core.unify/occurs? varp v expr binds) (throw (java.lang.IllegalStateException. (clojure.core/str "Cycle found in the path " expr))) (clojure.core.unify/bind-phase binds v expr)))))
> , compiling:(clojure/core/unify.clj:82:18)

I suspect the problem is that Midje uses an old version of clojure.core.unify (0.5.2). I use that old version because a later versions (like the current 0.5.7) failed in my use case for no reason I could quickly understand.

Hard to help without more info.
 
So I decided to just stick with the older, working version because forced upgrades make kittens suffer. And I am a kitten. And also: by semver

I don't know that core.unify follows semver. I don't know that it doesn't, but that seems like an assumption.
 
, why should a satisfied user of 0.5.2 care about 0.5.7?

 
(Note: I don’t know whether clojure.core.unify 0.5.7 is conformant to whatever spec is breaking.)

It is. I released core.unify 0.5.7 about a month ago to fix the spec issue mentioned above.

Alex Miller

unread,
Aug 21, 2016, 9:24:20 PM8/21/16
to Clojure
On Sunday, August 21, 2016 at 5:28:57 PM UTC-5, lvh ‌ wrote:
FYI, while I disagree with your conclusion (I think we should go fix libraries instead), I ran into the same issue just now for roughly the same reason, except the thing that pulled in an old version of core.unify was core.typed, which pulls in 0.5.3 through core.contracts.

BTW, core.contracts was also updated to 0.0.6 last week to use the latest core.unify.

Luc

unread,
Aug 22, 2016, 5:41:44 AM8/22/16
to Clojure
That emacs joke gets my week started with some abdominal pain 😂😂
I support strictness 😬
Luc P.

Alex Miller

unread,
Aug 22, 2016, 11:08:57 AM8/22/16
to Clojure
I've added library related fixes related to core specs to an info page at:

Leon Grapenthin

unread,
Aug 22, 2016, 12:23:33 PM8/22/16
to Clojure
I welcome the strict checking over backwards compatibility for broken syntax. E. g. allowing things like symbols in the ns decl would require supporting that as a feature in future updates, analyzer code, other hosts etc. The Clojure devs should not have to worry things with so little use.

Still the error messages are simply far from good enough and that is what appears to me as the main problem OP has. If the compiler had pointed him to two fixes he could have done in a minute then he had not complained and instead felt more happy with his new compiler. 

Here is my story when I loaded a large codebase today with 1.9-alpha11:

Call to clojure.core/defn did not conform to spec: In: [1 0] val:
   
({:keys [money/major money/minor money/currency], :or #:money{major
   
0, minor 0, currency :EUR}}) fails spec:

   
:clojure.core.specs/arg-list at: [:args :bs :arity-1 :args]

   predicate
: (cat :args (* :clojure.core.specs/binding-form) :varargs
   
(? (cat :amp #{(quote &)} :form
   
:clojure.core.specs/binding-form))), Extra input In: [1 0] val:
   
{:keys [money/major money/minor money/currency], :or #:money{major
   
0, minor 0, currency :EUR}} fails spec:
   
:clojure.core.specs/arg-list at: [:args :bs :arity-n :bodies :args]
   predicate
: vector?  :clojure.spec/args (format-money [{:keys
   
[money/major money/minor money/currency], :or #:money{major 0,
   minor
0, currency :EUR}}] (str major "," (if (zero? minor) "-"
   minor
) " €"))


I know where the problem is immediately  because I looked at above error and quickly jumped to the code that didn't work. Then I guessed it right because I know what has been changed from Alex Release notes and because I had recently inquired on this board about :or destructoring and probably because I am a long time Clojure user. The problem is that :or in map destructuring with namespaced keywords was not officially supported before 1.9 (but sadly worked exactly opposite to how it is supported now)

Compare that the more common story of someone who has not followed every newspiece lately and just wants to upgrade from 1.8 to 1.9 - How could he tell whats wrong from above error message? Following above error message by looking up specs and following index paths like [1 0] is a manual process that costs and feels like debugging a difficultly hidden bug. The time/utility distance to a hand written macro assert like "Keys in :or destructoring must be unqualified symbols" currently does not justify the use of specs for such things. It's by far worse than the NPE Stacktraces popping up from nowhere that one learns to value and utilize after a month or so in Clojure. 

It seems that improving the error messages we can calculate from specs data is something that more people should think about and improve for 1.9. I'd be willing to invest time if needed / input is welcome. Alternatively a way to integrate custom error messages into specs directly could also be helpful.
 
(But I still don't really see how above spec tells me that I shouldn't use qualified symbols in :or destructoring - do you?)

Brian Marick

unread,
Aug 22, 2016, 6:11:27 PM8/22/16
to clo...@googlegroups.com

On Aug 22, 2016, at 11:23 AM, Leon Grapenthin <grapent...@gmail.com> wrote:

Still the error messages are simply far from good enough and that is what appears to me as the main problem OP has. 

This is important. Will the new, stricter error messages be improved before 1.9 is finalized? 


Oliver George

unread,
Aug 22, 2016, 7:45:16 PM8/22/16
to Clojure

I'm interested to see any discussion regarding this point.  No doubt translating spec data into more friendly formats has been discussed.

Getting the data right is clojure's problem.  That's the concrete foundation and building blocks required for tooling.  Seems like Rich has done spectacularly there.

Potentially it's up to tooling to do more with that data.  I'd love to hear Bruce (figwheel), Collin's (cursive) and Bozhidar (cider) opinions about that.  

Colin Fleming

unread,
Aug 22, 2016, 8:43:53 PM8/22/16
to clo...@googlegroups.com
I agree that the ability to get a machine-readable parse failure is very important for tooling. However I feel very strongly that the error messages that are printed by default on macro validation failures should be easily understandable, and the current ones are not. If we completely punt to tooling for this, firstly users will receive different (and different quality) error messages depending on the tool they're using, and anyone using plain vanilla Clojure will not get good errors at all.

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Alex Miller

unread,
Aug 22, 2016, 8:50:19 PM8/22/16
to Clojure
I've already mentioned most of this above, but I'll try again. In short, I'd say yes (that's why we are still in alphas), but in adherence with the general goals we have of capturing and returning comprehensive data about the error and building those error messages generically.

- Getting the error data (specifically the explain-data output) to be both sufficient and generically useful is the first priority. I think at this point that's pretty close and unlikely to change significantly. This is the key data that can be used to drive everything else. form/describe are also tools leveraged by explain-data and there are a bunch of (known) problems still in those areas that will be cleaned up.
- The explain error message strings are currently pretty good on simpler specs. The big syntax macro cases like ns or let are way past the "average" spec. They are difficult in several dimensions - fan-out, recursion, large input forms, etc. All of those make creating generic error messages a lot harder so I don't think it's fair to judge the general performance of spec's errors on just those use cases. There are some general known problem areas where there are things that can be done. I talked about some of those in relation to s/cat in prior post, but there are others as well.
- It's unlikely that we will add any kind of "custom error strings" - the focus will be on automatic message generation.
- There are issues orthogonal to spec around compiler error reporting and stack trace display that can and should be improved.
- You've complained in other channels about the "learning to read" error messages part and I think you've taken it entirely the wrong way or maybe I just disagree. There are benefits from reporting errors in a generic, consistent way. That way may mean acquiring familiarity with something new, just like learning any programming language. I'm not disagreeing that the messages we're looking at are harder to understand than a hand-written custom error message for a particular specific situation, but we have bigger goals of automatically producing good errors for a large class of specs.  Keep in mind that while we're focusing here on one or two specific problems, these specs also are detecting a large number of other errors and generically producing error messages for all of them, while simultaneously being available to conform/destructure the input in ways that can potentially improve the implementations too.

All of this post is of course just my thoughts from someone in the room but not making the final decisions, so everything here is subject to being over-ruled tomorrow. :)

Alex Miller

unread,
Aug 22, 2016, 8:54:02 PM8/22/16
to Clojure


On Monday, August 22, 2016 at 6:45:16 PM UTC-5, Oliver George wrote:

I'm interested to see any discussion regarding this point.  No doubt translating spec data into more friendly formats has been discussed.

Getting the data right is clojure's problem.  That's the concrete foundation and building blocks required for tooling.  Seems like Rich has done spectacularly there.

Potentially it's up to tooling to do more with that data.  I'd love to hear Bruce (figwheel), Collin's (cursive) and Bozhidar (cider) opinions about that.  

Bruce has done some stuff with custom explain error reporting around cljs config (I think?) that was interesting. And I know of others that have built things too. I think those are cool and there are likely to be good places where they're useful, particularly in graphical editor environments where there are opportunities to do special stuff. But the intention is that the default messages should be useful without plugging something else in.

Alex Miller

unread,
Aug 22, 2016, 8:55:28 PM8/22/16
to Clojure

On Monday, August 22, 2016 at 7:43:53 PM UTC-5, Colin Fleming wrote:
I agree that the ability to get a machine-readable parse failure is very important for tooling. However I feel very strongly that the error messages that are printed by default on macro validation failures should be easily understandable, and the current ones are not. If we completely punt to tooling for this, firstly users will receive different (and different quality) error messages depending on the tool they're using, and anyone using plain vanilla Clojure will not get good errors at all.

I largely agree.. 

Colin Fleming

unread,
Aug 22, 2016, 9:00:29 PM8/22/16
to clo...@googlegroups.com
The big syntax macro cases like ns or let are way past the "average" spec. ... I don't think it's fair to judge the general performance of spec's errors on just those use cases.

It might be true that these macros are larger than usual, but they're also the cases that everyone will encounter all the time, particularly new users trying to figure out the language syntax.

--

Alex Miller

unread,
Aug 22, 2016, 9:27:28 PM8/22/16
to Clojure
Sorry, I missed this one in the thread somehow. This happens to be a case where you have *both* defn and destructuring specs in play, so it has even greater potential for confusion in generic errors. 

On Monday, August 22, 2016 at 11:23:33 AM UTC-5, Leon Grapenthin wrote:
I welcome the strict checking over backwards compatibility for broken syntax. E. g. allowing things like symbols in the ns decl would require supporting that as a feature in future updates, analyzer code, other hosts etc. The Clojure devs should not have to worry things with so little use.

Still the error messages are simply far from good enough and that is what appears to me as the main problem OP has. If the compiler had pointed him to two fixes he could have done in a minute then he had not complained and instead felt more happy with his new compiler. 

Here is my story when I loaded a large codebase today with 1.9-alpha11:

I'm going to reformat this a little (and formatting is certainly a thing that could change - since there are user values in here it's easy for the spacing to not be the best)...

Call to clojure.core/defn did not conform to spec:

You mentioned you knew exactly the location and the above gives you the form that is failing to match, so you've narrowed it down to a particular form in a particular file.

In: [1 0]
val: ({:keys [money/major money/minor money/currency], :or #:money{major 0, minor 0, currency :EUR}})

here's the actual value that is failing - so this does include the part that's wrong (:or map with non-symbol keys)

fails spec:  :clojure.core.specs/arg-list 

here we name another spec - you can use this to (doc :clojure.core.specs/arg-list) to get more info (and so on). There will be probably be some kind of a way to do this in a "deep" way at some point.

at: [:args :bs :arity-1 :args]

the path through the specs - can be combined with the spec name above and the spec for defn to give you a lot more info - this is the kind of thing that a sideband tool in an IDE could do amazing things with.

predicate: (cat :args (* :clojure.core.specs/binding-form) :varargs (? (cat :amp #{(quote &)} :form :clojure.core.specs/binding-form))), 

the predicate that is actually failing in the spec, probably not particularly helpful given the complexity (and recursiveness) of the destructuring specs

Extra input 

this is the part of cat that I think could be made more explicit - could be saying here that the value it had (above) was expected to match the next part of the cat (binding-form). So that could say the equivalent of "Expected binding-form but had non-matching value ..." and could even find what parts of that value matched and maybe which didn't (the :or keys) such that you'd have a more precise description. There is some more stuff Rich and I have worked on around "hybrid maps" which is the case here with map destructuring - it's particularly challenging to get a good error out of that at the moment, but there's more that can be done.

In: [1 0] 
val: {:keys [money/major money/minor money/currency], :or #:money{major 0, minor 0, currency :EUR}} 
fails spec:  :clojure.core.specs/arg-list 
at: [:args :bs :arity-n :bodies :args]
predicate: vector?  

This whole block is another (non-applicable) alternative that spec tried out (the multi-arity case). We know that's not what we were trying to do, but it's reported anyways. This is where fan-out kicks in and you can get a lot of noise that's not applicable. I think there's probably enough information to either be better about saying which case we thought you were matching or to hide some of them, etc.

:clojure.spec/args (format-money [{:keys
   [money/major money/minor money/currency], :or #:money{major 0,
   minor 0, currency :EUR}}] (str major "," (if (zero? minor) "-"
   minor) " €"))

And this is just reporting the actual passed args to the macro - I think this is confusing because it doesn't look like the call (because it's missing the macro name). As I said prior, I think this particular part of macro error reporting can do better as it knows more about what's going on - this should really be tied back up to the macro name at the top.

Andy Fingerhut

unread,
Aug 23, 2016, 3:58:42 AM8/23/16
to clo...@googlegroups.com
In the data representing fragments of the user's code returned with these macro errors, does it include metadata with :line and :column keys in it?

Perhaps that would be one way to give errors localized to particular places in the user's code.

It isn't always available, e.g. keyword cannot have metadata, but symbols, lists, vectors, and maps can.

Andy


--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Alex Miller

unread,
Aug 23, 2016, 9:18:23 AM8/23/16
to Clojure


On Tuesday, August 23, 2016 at 2:58:42 AM UTC-5, Andy Fingerhut wrote:
In the data representing fragments of the user's code returned with these macro errors, does it include metadata with :line and :column keys in it?

No, although the exception itself from macroexpansion will have the file/line info for the beginning of the failing form and the "at" part of the error gives the path in the data down to the value in question. So there is a lot of info available. 

Brian Marick

unread,
Aug 23, 2016, 9:49:38 AM8/23/16
to clo...@googlegroups.com

On Aug 22, 2016, at 7:50 PM, Alex Miller <al...@puredanger.com> wrote:

You've complained in other channels about the "learning to read" error messages part and I think you've taken it entirely the wrong way or maybe I just disagree. There are benefits from reporting errors in a generic, consistent way. […]

Do there exist examples of what is desired for error messages in 1.9-final? Not promises, but a “this is what we’re shooting for”? What would you all like the specific error messages complained about in this thread to look like? 

Colin Fleming wrote: "The error message produced by the code I demoed at the conj last year would be:

Unexpected symbol 'require' at <exact error location> while parsing namespace clauses. Expected :refer-clojure, :require, :use, :import, :load or :gen-class.”

Is that the goal? I fear that the goal is that it should be my job to understand "(cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses)”. For what little it’s worth, I consider that completely unacceptable. 

- Getting the error data (specifically the explain-data output) to be both sufficient and generically useful is the first priority. I think at this point that's pretty close and unlikely to change significantly. 

My bias here is that I come from the learned-from-bitter-experience tradition that believes it’s very risky to (1) get the infrastructure right, and then (2) pop down the user-visible features on top of it. Very often, the infrastructure turns out to be a poor match for the actual needs of the features. But, since (1) is already done, the features - and consequently the users - suffer. 

Please understand I’m not being insulting when I say that everyone has weaknesses and blind spots, even undoubted geniuses. In Clojure, error messages and documentation (especially doc strings) have long been glaring weaknesses. So I am wishing to be helpful when I counsel *quickly* getting to worked examples of output, especially output that novices are likely to encounter. And exposing those messages to typical users, ones who are not familiar with core.spec. 

That seems prudent. 

I believe strongly enough in good error messages that I would be willing to do some of the scut work, if needed.

Timothy Baldridge

unread,
Aug 23, 2016, 10:21:36 AM8/23/16
to clo...@googlegroups.com
So personally, I don't want extremely accurate suggestions in the core of Clojure. Why? Because I think they will never go far enough and I have a ton of features I want to see that can't (shouldn't) be in the core of a language.

I'll never forget the first undefined symbol I got in Clang after switching from GCC. When I say "undefined local 'val' did you mean 'vals'?". I was floored, what was this magic? 

When I sit down and write a laundry list of things I would like to see in error reporting for spec: 
- Spelling suggestions: "did you mean :require instead of :requir?"
- Type suggestions: "you passed a symbol did you want a keyword?"
- Phonetic suggestions: "you passed :color, did you mean :colour?"
- Structural suggestions: "you passed [[42]] did you mean [42]?"

Most (all) of these are way too broad for inclusion in the core of a language (imo), not to mention the problems you'd hit with multi-lingual support. So I say to all that, give me data and let 3rd parties write the libraries. I'd much rather have a 200MB "error reporter" lib in my {:profiles {:dev ...}} than included in the clojure.jar. 

Anyway, that's my 2cents. And if someone doesn't write that library for me soon, I may have to :)

Timothy

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Alex Miller

unread,
Aug 23, 2016, 10:45:07 AM8/23/16
to Clojure
I do not have an idea of what the final end point will look like exactly. I don't get the feeling that there is any answer that you will find satisfying, so I'm not sure what else I can do for you. We expect Clojure users to become familiar with spec and its output as it is (now) an essential part of the language. You will see specs in error messages. 

The focus in Clojure has always been biased towards building a powerful and expressive tool that is rewarding for experts vs optimizing for novices. Rich has talked at length about why that is (see https://www.infoq.com/presentations/design-composition-performance-keynote / https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/DesignCompositionPerformance.md in the section around languages as instruments). Pertinent bit (but you should watch the whole thing for context):

So we need players. I would rant here, but I won't. But look at this guitar player with blisters. A harpist has blisters, a bass player with blisters. There's this barrier to overcome for every musician. Imagine if you downloaded something from GitHub and it gave you blisters.

[Audience laughter]

Right? The horrors! And yet how many people here play an instrument or have at one point in their lives? Yeah, a lot of programmers do. And for how many people did you just pick it up and it was awesome? How many wished, like, something could have made it more straightforward to get started with and, like, just made it easy? And how many would have believed after that that they could play it later? No, not at all. This is - it's actually quite important. The level of engagement that's required is quite important.

So we shouldn't sell humanity short. Humans are incredible. In particular, they're incredible learners.

One of the things that's really cool is you give a five-year-old or, I don't know, eight, maybe, a cello and some decent instruction, and they will learn how to play cello if they spend enough time doing it. In fact, humans will pretty much learn how to do anything that they spend enough time doing. We're incredibly good at it.

And we're also really good teachers, in general. So I don't think we need to go to our tools and our instruments and make them oriented towards the first five seconds of people's experience because that's not going to serve them well. It's especially not going to serve anyone well who wants to achieve any kind of virtuosic ability with the tools. No one would become a virtuoso on the cello if they had red and green lights when they started.

So neither of these two things is effort free, but we shouldn't be in a game to try to eliminate effort because we are novices, right?

There's a sense in which we're only going to briefly be novices.

You're only a complete beginning at something for an incredibly short period of time, and then you're over it.

It's like we should not optimize for that. But, on the flipside, we're always learners no matter how much time you spend on the violin. Who sits there and says, "I'm done. I've completed learning violin. I finished it"? That's awesome. I personally don't play violin at all, but I don't think there would be a player on earth, no matter how great they are, who would say, "Yeah, I finished violin and I moved on to something else." We're constantly. It's just the human condition to do this.

Things take effort. Just like we shouldn't target beginners, we shouldn't try to eliminate all effort.

...and there's more there - it's really worth reading/watching the whole thing. We are not apologetic about this bias. We expect you to engage and learn this tool that you're going to use for serious work because there's also deep payoff on the other side, just like learning to play the guitar or is more rewarding than learning to play the kazoo. 

I'm absolutely not talking about making something hard on purpose and I'm not saying that making things easy to learn is bad. I'm stating an ordering of priorities. It's more important to us to build a system of many parts that can be composed together into specifications that work as validators, and conformers, and sample generators, and error explainers, etc. We *also* want the automatic errors created from that to be useful and helpful and understandable thus this is a WIP. But creating error messages that are optimal for a user with no knowledge or Clojure or spec is just not the goal.

Elena Machkasova has been doing research (supported in part by Cognitect) on figuring out what totally new users of Clojure need from error messages for her CS education classes and the answer there is just different from what an experienced user needs. That's ok. We care more about the latter.

kovas boguta

unread,
Aug 23, 2016, 11:36:10 AM8/23/16
to clo...@googlegroups.com
As some other people have stated:

Its way, way premature to start worrying about the exact format of error messages.

Given the facilities spec provides, its clear as day that vastly better messages can be built on top. Or even forget messages: syntax highlighting or source-code presentation can encode the same information without wading through text. 

Nothing is stopping people from attaching additional information to functions or keywords to help guide a reporting process. 

Its a great time for experimentation, if you have a better idea how to report the errors. 








--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Sean Corfield

unread,
Aug 23, 2016, 3:12:38 PM8/23/16
to Clojure Mailing List
On 8/23/16, 7:45 AM, "Alex Miller" <clo...@googlegroups.com on behalf of al...@puredanger.com> wrote:
> I'm absolutely not talking about making something hard on purpose
> and I'm not saying that making things easy to learn is bad. I'm
> stating an ordering of priorities.

…and this is why I think it’s more important that Clojure is as “simple” and consistent as possible, so that when you are a beginner with Clojure, there are fewer surprises and the rules you need to learn are more straightforward.

Despite clojure.spec messages not being “easy” to read at first – because they are unfamiliar – they are “simple” insofar as they are clearly structured and consistent and can be traced thru using just the ‘doc’ function.

Sean



Colin Fleming

unread,
Aug 23, 2016, 3:17:11 PM8/23/16
to clo...@googlegroups.com
The error message I posted earlier comes right out of the parser with very little infrastructure on top of it. It certainly doesn't require a 200MB jar, more like 300 lines of code, including the parser itself.

Timothy Baldridge

unread,
Aug 23, 2016, 3:21:16 PM8/23/16
to clo...@googlegroups.com
>> It certainly doesn't require a 200MB jar, more like 300 lines of code, including the parser itself.
I completely agree, what I'm saying is, if you start supporting my laundry list of phonetic, spelling, and structural suggestions, your codesize will quickly get much larger than 300 lines.

Mark Engelberg

unread,
Aug 23, 2016, 3:33:34 PM8/23/16
to clojure
On Tue, Aug 23, 2016 at 7:45 AM, Alex Miller <al...@puredanger.com> wrote:
We expect Clojure users to become familiar with spec and its output as it is (now) an essential part of the language. You will see specs in error messages. 

Is there any documentation to help users understand how to interpret the error messages?  For example, what is the "path" returned by spec errors?  I've been through the spec guide, and don't recall seeing anything like that. I've been able to make a few deductions on my own, but without a better mental model, the messages are still pretty mystifying to me.

Alex Miller

unread,
Aug 23, 2016, 4:36:00 PM8/23/16
to Clojure
The path is the series of tags you've traversed through the spec (when there are parts in :or :alt :cat etc).

We will have more documentation on it but we've held off because it was changing pretty regularly in early alphas.

A spec for the current explain-data is something like this (I'm just typing this in, so could be a bit off):

;; path is a stack of parts, which refer to tags in schemas
(s/def ::part simple-keyword?)
(s/def ::path (s/coll-of ::part :kind vector?))

;; pred is a symbol, a set, or a function, but being lazy here
(s/def ::pred any?)

;; the failing data value
(s/def ::val any?)

;; via is a stack or schema identifiers
(s/def ::schema-key qualified-ident?)
(s/def ::via (s/coll-of ::schema-key :kind vector?))

;; in is a stack of keys in nested data
(s/def ::data-key any?)
(s/def ::in (s/coll-of ::data-key :kind vector?))

;; some of these may be required, I would have to analyze the code further
(s/def ::problem
  (s/keys :opt-un [::path ::pred ::val ::via ::in]))
(s/def ::problems
  (s/coll-of ::problem :min-count 1))

;; added when macroexpand args check fails
(s/def ::args
  (s/coll-of any?))

(s/def ::explain-data
  (s/keys :req [::problems] :opt [::args]))

Beau Fabry

unread,
Aug 23, 2016, 5:03:32 PM8/23/16
to Clojure
While I think the spec errors are pretty unfriendly, it's probably worth remembering that most of the times you'll get one you would have got an inscrutable ClassCastException or incorrect behaviour from a totally different place in the codebase prior to spec. It's definitely a huge improvement on that.

The :require issue was an unfortunate first encounter with a spec error because it caught a situation that wasn't actually causing a problem, I think most spec errors users see in the wild that won't be true for.

Colin Fleming

unread,
Aug 23, 2016, 7:09:48 PM8/23/16
to clo...@googlegroups.com
But creating error messages that are optimal for a user with no knowledge or Clojure or spec is just not the goal.

This is a totally false dichotomy. No-one in this thread is asking for that. This thread has several examples of expert Clojure users for whom the error messages are incomprehensible.

I am equally unapologetic about thinking that the musical instrument analogy is mostly bogus here. There are things that will always be difficult about learning Clojure because they're conceptual, such as functional programming. I think the analogy is fair there, they are just things that will require effort and practice to learn. But the error messages are about giving people the information they need so that they can actually learn from their mistakes. Clojure has historically been appallingly bad at that, and no-one should expect their users to flail around randomly trying things to see what works. I've spoken to various smart people who have described their experience of using Clojure as exactly that, even after a non-trivial amount of time using it. I hope spec can improve on that experience.


--
Message has been deleted

Mond Ray

unread,
Aug 24, 2016, 5:32:42 AM8/24/16
to Clojure
I agree Colin, this feels more like the beatings shall continue until morale improves ;-)

More seriously, I understand the point of the musical instruments analogy to be a reminder to programmers that learning a language and understanding it in depth will increase your power and expressivity with that language. That should not be used as a reason to increase the difficulties caused by obscure error reporting. My initial understanding of the sales pitch for specs was that it would serve to improve error messages as the macro expansions / transformations would be more tractable in the compiler. I get that it is a work in progress but let's retain that original goal.

Unlike you however, I would prefer correctness and the consequent ripples over the continuing acceptance of incorrect expressions. My reasoning is that code which has fewer compatibility style branches will be easier to equip with the necessary instrumentation for generating more human friendly error messages.

Ray

PS I think this require vs :require thing comes from the way that novices confuse the ns macro with the function that pulls dependencies in at the REPL. Cutting / pasting between the REPL and the file can allow that to bleed in. I know it confused me.

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Colin Fleming

unread,
Aug 24, 2016, 5:58:31 AM8/24/16
to clo...@googlegroups.com
Sure, at the end of the day I don't really care about thre require/:require issue, it just seems a little incongruent with previous decisions which have promoted backwards compatibility. I generally prefer increased strictness, so I'm fine with the change. I do care about the error messages, though.


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Stuart Halloway

unread,
Aug 24, 2016, 9:39:51 AM8/24/16
to clo...@googlegroups.com
Brian originally raised 5 points that were concrete & specific, and therefore potentially actionable. That is usefully-shaped feedback, thanks Brian!  My take on those points, which I will recast in my own words:

1. "Loosen rules about ns form to match what people have actually done."  This is pretty unlikely, for reasons already covered.

2. "You can tailor a much better specific message for 'require should be a keyword' than what spec makes today." There are several possible things to explore here. The most interesting one is "can spec come closer to a bespoke message while maintaining its simplicity and composability?"  We want to make all errors better, not one error awesome. Ideas welcome!  

3. "Follow the inverted pyramid so people see what is most important."  This kind of thing is easily done in a layer above spec, e.g. a custom REPL printer for spec macro errors. Worth working on but not critical to getting spec right.

4. "Name the problem namespace."  Spec does way better than this already, finding the precise file and line.  If there are places where this is busted we should fix them.

5. "I don't want to see the stack trace."  Then filter it out of your REPL.  Intermediaries should never discard telemetry, but end consumers can choose to.

Cheers,
Stu


Andy Fingerhut

unread,
Aug 24, 2016, 10:27:45 AM8/24/16
to clo...@googlegroups.com
A suggestion for making all errors better would be to give not only the precise file and line _of the beginning of the top level form containing the problem_, but a more precise line and column of _the part of the form that spec is complaining about_.  Multi-line forms are the biggest and hardest to figure out which part spec is complaining about.

Andy

Beau Fabry

unread,
Aug 24, 2016, 1:17:58 PM8/24/16
to Clojure
Just specifically on a custom REPL printer, wouldn't that negate the benefits Alex sees in people becoming accustomed to reading spec error messages? If every error report from each different environment potentially looks different? Also, from the position of a community maintainer Brian is most commonly going to be seeing the lowest common denominator error messages in bug reports filed, and it probably wouldn't be helpful for him to be getting multiple representations of the same error in different reports.

Stuart Halloway

unread,
Aug 24, 2016, 5:03:41 PM8/24/16
to clo...@googlegroups.com
Hi Beau,

Yes. Nevermind and everyone should learn to read spec. :-)

That said, such customizations allow people to experiment and flesh out a bunch different ideas in parallel.

Best,
Stu
 


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Leon Grapenthin

unread,
Aug 24, 2016, 5:33:43 PM8/24/16
to Clojure


On Tuesday, August 23, 2016 at 3:27:28 AM UTC+2, Alex Miller wrote:
predicate: (cat :args (* :clojure.core.specs/binding-form) :varargs (? (cat :amp #{(quote &)} :form :clojure.core.specs/binding-form))), 

the predicate that is actually failing in the spec, probably not particularly helpful given the complexity (and recursiveness) of the destructuring specs

Extra input 

this is the part of cat that I think could be made more explicit - could be saying here that the value it had (above) was expected to match the next part of the cat (binding-form). So that could say the equivalent of "Expected binding-form but had non-matching value ..." and could even find what parts of that value matched and maybe which didn't (the :or keys) such that you'd have a more precise description. There is some more stuff Rich and I have worked on around "hybrid maps" which is the case here with map destructuring - it's particularly challenging to get a good error out of that at the moment, but there's more that can be done.

Thank you for doing the walkthrough. I observed this too and became sceptical why spec doesn't go further down the path and apparently stops at ::binding-form.
I could isolate the problem a bit by changing the spec of ::arg-list and temporarily removing the :varargs branch.

(s/def ::arg-list
  (s/and
    vector?
    (s/cat :args (s/* ::binding-form)
    ;;       :varargs (s/? (s/cat :amp #{'&} :form ::binding-form))
           )))

This leads to a much better message:

(s/explain (:args (s/get-spec 'clojure.core/defn))
           '[foo [{:or {a/b 42}}]])

In: [1 0] val: {:or #:a{b 42}} fails spec: :clojure.core.specs/local-name at: [:bs :arity-1 :args :args :sym] predicate: simple-symbol?
In: [1 0 0] val: ([:or #:a{b 42}]) fails spec: :clojure.core.specs/seq-binding-form at: [:bs :arity-1 :args :args :seq] predicate: (cat :elems (* :clojure.core.specs/binding-form) :rest (? (cat :amp #{(quote &)} :form :clojure.core.specs/binding-form)) :as (? (cat :as #{:as} :sym :clojure.core.specs/local-name))),  Extra input
In: [1 0 :or a/b 0] val: a/b fails spec: :clojure.core.specs/or at: [:bs :arity-1 :args :args :map :or 0] predicate: simple-symbol?
In: [1 0] val: {:or #:a{b 42}} fails spec: :clojure.core.specs/arg-list at: [:bs :arity-n :bodies :args] predicate: vector?

The third one is the desired one and very precise - it seems to be usually the one with the largest :in path. The length of the :in path seems a good sorting criterium for reporting.

However I was not able to track the issue further down. I also wasn't able to reproduce a more minimal case of this problem. It seems like a bug in how spec parses and must have something to do with s/cat and the :varargs branch.

Leon Grapenthin

unread,
Aug 24, 2016, 7:21:59 PM8/24/16
to Clojure
Hi Alex, I could track down why explain stops early. http://dev.clojure.org/jira/browse/CLJ-2013

Colin Fleming

unread,
Aug 24, 2016, 8:32:02 PM8/24/16
to clo...@googlegroups.com
This is almost exactly the intuition behind the standard error reporting heuristic for grammars involving alternations. It is a heuristic, but it has to be since on a failure it's impossible to entirely accurately determine the user's intention. But intuitively, generally the rule that has managed to parse the furthest into the input is considered the rule that is most likely to be what the user intended since that's the one that has matched the most. This is usually calculated using the source offset, this is subtly different to :path since :path is the distance in the grammar itself. This doesn't necessarily correspond to the distance into the input - consider the regex aabcd|a* when matching aaaa - the first alternation would produce a path of length 2 if the two a's in the grammar are labeled, but the second would produce a path of length 1 (a single rule) even though it would match more input.

Here's how the basic algorithm works to produce an error like I showed above.
  1. Parse the form - if it matches some path through your grammar, you're done.
  2. If not, for each failure record the path to that failure, the text offset of the error point and what the rule was expecting at that point. This may be that you found something unexpected, but may also be that you ran out of input and needed more.
  3. When done, find the furthest error point and collect all the failure info for rules which failed at that point.
  4. If there's just a single failed rule, use that as your error rule to report from. Otherwise, use a heuristic like "find the shortest subset of all failed rules and use that path as the failing rule" (since all failures start from that point).
  5. Report something like "Error when parsing <rule from #4>, found <what you found at the error point> but was expecting one of <the set of all elements that failed rules were expecting at that point>".
There are some complications here in Clojure since we're parsing forms, and not all form elements have metadata saying where they exist in the source (e.g. keywords). Also, when you run out of input it's really useful to be able to indicate the end of the form you were parsing to say "I needed more input here". Finally, if we're parsing the result of a previous macroexpansion, macros are historically pretty bad at propagating source metadata. I'd like to see the following improvements:
  1. Ensure that forms always get source metadata added - I believe there were some bugs around this.
  2. For composite forms (i.e. collection literals) add metadata indicating the end of the form in the source.
  3. On macroexpansion, automatically propagate the source location metadata from the source form to the expansion if it's not present there - that way at least the form itself will retain it.
tools.reader handles all this stuff a lot better, I believe. I think there are also open JIRAs for some of this work.

--

Brian Marick

unread,
Aug 24, 2016, 8:46:47 PM8/24/16
to clo...@googlegroups.com

On Aug 24, 2016, at 8:39 AM, Stuart Halloway <stuart....@gmail.com> wrote:

3. "Follow the inverted pyramid so people see what is most important."  This kind of thing is easily done in a layer above spec, e.g. a custom REPL printer for spec macro errors. Worth working on but not critical to getting spec right.

So why not do it in the bottom layer? Is there some deep reason why only an unserious programmer would want information in anything other than the current clojure.spec order? (We’re talking here about reordering a list.)

There has been a notable lack of “yeah, we might have made a sub-optimal decision” in this discussion. It looks bad, in my opinion. Has looked bad for a long time. 


Brian Marick

unread,
Aug 24, 2016, 9:35:01 PM8/24/16
to clo...@googlegroups.com

> On Aug 24, 2016, at 7:46 PM, Brian Marick <mar...@roundingpegs.com> wrote:
> So why not do it in the bottom layer? Is there some deep reason why only an unserious programmer would want information in anything other than the current clojure.spec order? (We’re talking here about reordering a list.)

An even crazier idea: given that there are N difference pieces of information, they could be presented in a map, with keys that described each of them.

adrian...@mail.yu.edu

unread,
Aug 24, 2016, 10:28:17 PM8/24/16
to Clojure
I do not think your tone and lack of constructive feedback to Alex's (and others) thoughtful responses is helping your case. 

Stuart Halloway

unread,
Aug 24, 2016, 10:41:49 PM8/24/16
to clo...@googlegroups.com
Brian,

The tone of your previous post is not constructive. Let's keep the discussion about ideas, not people.

Thanks,
Stu


Stuart Halloway

unread,
Aug 24, 2016, 10:45:58 PM8/24/16
to clo...@googlegroups.com
Hi Brian,

Not crazy at all!  Spec errors are maps at the bottom, and IMO these maps should flow anywhere we are making exceptions.  This is already true for the exceptions coming from spec.test, and we should make it true for the macroexpand exceptions as well. (I actually prefer reading the map format, IIRC it is what I show in most places in the screencasts.)

Thank you for pointing this out.

Regards,
Stu

Brian Marick

unread,
Aug 25, 2016, 11:18:28 AM8/25/16
to clo...@googlegroups.com

On Aug 24, 2016, at 9:28 PM, adrian...@mail.yu.edu wrote:

I do not think your tone and lack of constructive feedback to Alex's (and others) thoughtful responses is helping your case. 

Probably not(*), though I would characterize the responses differently. They are polite, and they are intended to be helpful to someone who already agrees with axioms like “good error-handling is a nail for which core.spec is the hammer” and “it is wise to push the responsibility for error understanding to third-party libraries or diligent study”. They do a service in that they lay out the rules under which Clojure users should expect to live. But they are largely reiterations rather than engagement. I find that rather frustrating.


Let me point to an essential book on business/community management, Hirschman’s /Exit, Voice, and Loyalty/. https://en.wikipedia.org/wiki/Exit,_Voice,_and_Loyalty, and to a clever take on group behavior, “Evaporative Cooling of Group Beliefs”, http://lesswrong.com/lw/lr/evaporative_cooling_of_group_beliefs/. I think there is much to learn from reflecting on those and the direction of Clojure design and the Clojure community over the past few years. (I’m not a huge fan of the application of Satir’s family counseling theory to software management - Gerald Weinberg and the like - but it’s hard not to read books like the /Quality Software Management/ series and see people in the Clojure community - including me! - playing out stereotypical dysfunctional roles.) 

Read me as someone who’s publicly and self-destructively giving up on Voice and is on the way to Exit. As someone who tends to Loyalty (though perhaps the loyalty of the traditional Catholic Devil’s Advocate), it’s rather agonizing. That is, I still think Clojure is the best raw language out there for broad-spectrum work. However, its design has been doubling down on long-unfortunate tendencies, and - I’d argue - new languages like Rust, Elixir, and Elm (even Pony) - are raising the bar for both community management and “peripheral” concerns like documentation and error handling. In the meantime, the Clojure ideology - reinforced by memes like “complecting” - has been getting more rigid. 

The result is that what seem to me bizarre decisions are treated as normal. We have an `any?` in clojure.core that always returns `true`. This deviance from probably every other programming language is justified as obvious for a feature - clojure.spec - that is largely unproven, certainly when it comes to error reporting. (Even worse, we have `any?`, `some?`, and `some` - all idiosyncratic.) Yet the idea of changing the name of `any?` is completely dismissed, with the justification that people complain about every new name. (Think about what that decision criterion entails, broadly applied.)

Also bizarre: the idea that error messages that amount to essentially dumping a parse tree + BNF-ish grammar clause (possibly twice with a vomitous stack trace between) is a *good* thing. Not a “we wish we could do better, but software development is about constraints and tradeoffs” thing. Not a “yeah, but Rich Hickey doesn’t want to bother with that stuff” thing. 

(I was honestly flummoxed that, although clojure.spec is supposed to be the answer for error handling, there’s been no attempt to work through what good error messages would be like and how the current infrastructure would support the translation from raw data to such error messages.)

I cannot help but think of this as groupthink. And - to be grandiose - having people like me Exit will increase that, per evaporative cooling. 


I also note that my library, Midje, is typically insulted on this mailing list whenever a newbie brings it up. One of the contributors to this thread has called it “an abomination”. There was no similar concern about *his* tone. Because, I suspect, he's on the inside, punching out.

---

(*) Might that not be my fiendish plan? Perhaps I’m being abrasive on this list exactly to associate ideas like “error messages are the responsibility of the compiler” as being from a hated Other, thus hardening a position that I think is bad for Clojure. Why would I do that? Because I’m 90% likely to be going all-in on Elixir and Elm. Encouraging destructive behavior in a competitor language increases my languages' chances of success. Bwahaha! (Oops, just violated rules 6 and 7 of http://www.eviloverlord.com/lists/overlord.html )

adrian...@mail.yu.edu

unread,
Aug 25, 2016, 11:46:14 AM8/25/16
to Clojure
I really don't understand how you expect anyone to take your criticism seriously if you keep implying you're happily abandoning the language for greener pastures. 

Why would anyone developing Clojure look at anything you have to say at this point as anything less than trolling? 

Back on topic, I find Colin's suggestions about implementing an error reporting heuristic intriguing. What he has laid out could form the basis for a solution to this problem which can satisfy everyone's requirements when integrated well with spec's explain-data. I am curious to hear what Alex and others think about it. 

Alex Miller

unread,
Aug 25, 2016, 11:46:52 AM8/25/16
to Clojure
Brian, your concerns have been heard. Please keep in mind this is a work in progress and that there is ongoing work that is not yet visible.

While I don't expect that the final endpoint of this work will be exactly what you would design (or what you might see in other languages as our goals and priorities are different), I do think it will be better than it is now and vastly better than before spec existed.

I do not think you are a "hated Other". I welcome your contributions to the community. As a moderator of this list, I do not recall seeing the "abomination" comment on the mailing list, but I agree that that is inappropriate and unwelcome here. Disagreement is fine, disrespect is not. I regularly address comments like this either privately or publicly (depending on the situation) and I'm sorry I missed that one, so I apologize for that.

Alex Miller

unread,
Aug 25, 2016, 11:47:56 AM8/25/16
to Clojure
On Thursday, August 25, 2016 at 10:46:14 AM UTC-5, adrian...@mail.yu.edu wrote:
I really don't understand how you expect anyone to take your criticism seriously if you keep implying you're happily abandoning the language for greener pastures. 

Why would anyone developing Clojure look at anything you have to say at this point as anything less than trolling? 

Back on topic, I find Colin's suggestions about implementing an error reporting heuristic intriguing. What he has laid out could form the basis for a solution to this problem which can satisfy everyone's requirements when integrated well with spec's explain-data. I am curious to hear what Alex and others think about it. 

Working on many things and may make use of the idea.

Gary Trakhman

unread,
Aug 25, 2016, 11:55:09 AM8/25/16
to clo...@googlegroups.com
Over the years I've kind of started agreeing with what Brian's saying.  Much as I love/know clojure and the philosophy that bears its fruit, I think spec's sideband error-handling is a great low-risk opportunity to build in some 'easy'.

My team is moving from rails towards elixir after having seriously considered clojure (and hiring me recently under that premise), and I'm having to apologize for the general lack of novice guardrails, 'conventions' and documentation that people from other communities expect.  I think it looks pretty good if you're used to java (java conservatism notwithstanding), but not so good if you've been in dynlangs (particularly ruby) or other FP languages besides lisp.

I'm concerned the current approach will lead to too many half-baked error-reporters.  Alternatively, if there's a canonical human-facing error-reporter built on top of the more stable data representation, I think it would be generally acceptable to break 'contracts' there as we find better ways to show the errors.

--

Timothy Baldridge

unread,
Aug 25, 2016, 12:33:36 PM8/25/16
to clo...@googlegroups.com
>> I also note that my library, Midje, is typically insulted on this mailing list whenever a newbie brings it up. One of the contributors to this thread has called it “an abomination”. There was no similar concern about *his* tone. Because, I suspect, he's on the inside, punching out.

Yes, that was me. And since this seems to come up rather often I should probably address it. I scanned the archives and found the original statement I made:
"I don't recommend Midje at all. Many of the framework's mocking facilities (such as providing) are abominations. You shouldn't go around mucking with functions and re-deffing them on the fly. It may look cute, but I've lost countless hours to bugs and unexpected behavior related to Midje. IMO, stay clear of that. "

So to be clear, I called certain patterns in Midje: "abominations", not the library as a whole, and defiantly never you. And unfortunately I have to stand by characterization of these facilities. I strongly believe that new code evaluation semantics combined with mutation of var roots is a great way to add complexity to your testing suite. Changing var roots on-the-fly adds complexity that will come back to bite you when you add things like async calls and parallelism. Creating new evaluation semantics makes code impossible to read without fully understanding the interpreter of the DSL. What is executed first? What happens when? Who knows, it's a foreign language.

But please don't take my statements as a personal attack. I have written a lot of bad code in my life, some of it is used daily by clojure programmers. I often do code reviews, and I may come across as abrasive when discussing a bit of ugly code, but I bear no ill-will to the programmer. The same applies here, I may strongly dislike aspects of Midje, but I can dislike someone's work without disliking them. 

So now I'm completely off topic for this thread, but since my statement back in 2014 keeps coming back time and again, I figured it was best to clarify my words. And Brian, if we ever are at a conference together, let's sit down and I'll buy us a drink (or two). It'll probably do us both good to get to know each other better.


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Timothy Baldridge

unread,
Aug 25, 2016, 2:22:10 PM8/25/16
to clo...@googlegroups.com
After further consideration, I would like to back off the word: "abomination". I have strong opinions about code, and strong opinions about technical aspects of Midje, but I chose the wrong word in my original statement. The technical definitions of words do not matter as much as the connotations they carry in common parlance. I can see how my choice of wording here can be taken as a personal attack. And so for that, I do apologize, Brian. In the future I will attempt to use less loaded verbiage.

Timothy Baldridge

Stuart Halloway

unread,
Aug 25, 2016, 2:27:45 PM8/25/16
to clo...@googlegroups.com
Hi Gary,

Re the documentation: A lot of people have worked to make clojure.org better, including changing the contribution model to be both easier and more familiar.  

That said, I don't doubt that is could be a lot better.  In particular, the guides section could expand to cover a lot of the "convention"-type topics you allude to. You can contribute here: https://github.com/clojure/clojure-site.  And now also here: https://github.com/clojure/clojurescript-site.

Stu


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Colin Fleming

unread,
Aug 25, 2016, 7:12:04 PM8/25/16
to clo...@googlegroups.com
I really don't understand how you expect anyone to take your criticism seriously if you keep implying you're happily abandoning the language for greener pastures. 
Why would anyone developing Clojure look at anything you have to say at this point as anything less than trolling? 

Because if we're genuinely interested in improving the language and the community around it, the people who are deliberately choosing to leave it have the answers to what we need to do that.

I develop Cursive, and I'm always very interested in feedback from users, and I'm really more interested in feedback about what they don't like because my opportunities for improvement are there. It's always nice to hear that people love Cursive and those messages are an important part of maintaining my motivation to continue working on it. But in terms of feedback that I can directly action, problems are where it's at.

Of course, sometimes that feedback is not useful, actionable or valuable. "IDEs suck" gets ignored, in much the same way that "Clojure sucks because it's dynamically typed" should. "I don't use it because I find IDE's too heavy" (like "I don't use Clojure because I prefer strong types") is fine, there are lots of people out there with different preferences and I can't cater to everyone. Being based on IntelliJ is what Cursive is, and I can't change that. But if someone uses Cursive a lot, likes it, talks publicly about how much they like it, participates in the issue tracker and on the mailing list etc etc but then says "I'm taking up Emacs because I can't stand how Cursive does x, y, and z" then I will absolutely try to make those things better. 

Criticisms about how the community works are perhaps the hardest to hear since they seem very personal - if we're active in the community, in some way they're directly criticising the ways we behave. Similarly, they're much harder to fix. But they are essential, since no-one uses a programming language purely because of the language itself these days.

FWIW, I share many of Brian's concerns.

--

adrian...@mail.yu.edu

unread,
Aug 25, 2016, 8:03:31 PM8/25/16
to Clojure
Colin,

FWIW, I think you're doing a great job of articulating your points (which I largely agree with) and are providing great feedback for the core team and community to think about. This conversation is supposed to happen as the alpha versions are being iterated on. 

But I think continually resurfacing meta issues one has with Clojure's management (and I'm not saying you're doing this, but others are) instead of engaging thoughtfully with a team who is engaging you thoughtfully is both disrespectful and unproductive. Actually, it's counter productive because it can unfortunately make people think twice before taking you seriously in the future.

Like you, I think friendly error messages is an important issue for Clojure. We have an amazing source of heavily annotated information we can use to generate best-in-class error messages thanks to clojure.spec. We should refocus this in thread. 

But I'm not a moderator, so I guess take whatever I say with a grain of salt. :) 


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Rick Moynihan

unread,
Aug 25, 2016, 9:00:37 PM8/25/16
to Clojure
I think one obvious area that specs error messages could be improved is with some basic formatting and cosmetic changes. If spec presented errors not as a wall of text and syntax but with some simple formatting it would make a big difference to legibility.

As a starter for 10, why could we not render the messages at a REPL more like this?  (Note this is basically Brian's error - re-rendered):

user=> (ns such.sequences (require ,,,,))

CompilerException clojure.lang.SpecException:

Call to clojure.core/ns did not conform to fdef [:args] spec

There was unexpected extra input in: [2]

with value: (,,, (require [such.vars :as var]
                                     [such.immigration :as immigrate])
                       (require midje.checking.checkers.defining
                                    midje.checking.checkers.chatty
                                    midje.checking.checkers.simple
                                    midje.checking.checkers.combining
                                    midje.checking.checkers.collection))

Input failed spec predicate: (cat :attr-map (? map?)
                                                   :clauses :clojure.core.specs/ns-clauses)

When compiling: (such/sequences.clj:1:1)

user=>

Some things to point out:

1. Provide some extra context by subclassing IllegalArgumentException as SpecException.  This may also help separate SpecException's from other IllegalArgumentExceptions too, and help tools do something special on a SpecException.

2. Use of new lines to break up and separate text blocks.

3. State that it's an fdef spec, and it was the [:args] bit of that spec that failed.  By stating them together we implicitly associate [:args] with fdef.  Note I'm assuming we can also capture that fdef defined this spec.

4. It's a bit unclear what "Extra input" means... so clarify that it was unexpected.  Provide the path [2] as before.

5. State the failing value and pretty print it.  Note that we also elide the other passing parameters with a ,,,,

6. As before state the predicate that identified the failure in a humanised manner and the location of the failing form.

I'm not suggesting these are necessarily good ideas, and I appreciate I've not considered all of the other cases you might need to, but it seems that something like the above would be a dramatic if entirely cosmetic improvement.  It would be a shame if clojure.core made no attempt to humanise the display of these messages and left it entirely up to the community.

Thoughts?

R.


On 20 August 2016 at 15:03, Alex Miller <al...@puredanger.com> wrote:


On Saturday, August 20, 2016 at 5:17:59 AM UTC-5, Brian Marick wrote:
Yesterday, a bug was filed against Suchwow under 1.9alpha11. It turns out to have been a use of `ns …(require…` instead of `(ns …(:require`. Not in Suchwow, but in Midje. Unfortunately, the Suchwow file the bug report pointed at *also* had that typo - apparently I am prone to it - so adding the colon to the require there didn’t make the problem go away. 

That caused me to lose my temper and make a fool of myself, which is neither here nor there, except that I apologize to @puredanger. 

I have two suggestions, though:

1. It has long been the case that Clojure allowed `(ns (require…)` even though that’s strictly incorrect. I suggest that, for backwards compatibility, it be allowed going forward. That is, I think it does no harm for a correct `ns` statement to allow symbols as well as keywords. That wrong code in Midje has been there since Clojure 1.2. 

We discussed this before releasing the specs and decided to start on the strict side. That said, this is still an alpha and there is plenty of time to change our minds prior to official release of 1.9 if that ends up being a catastrophic decision.
 

2. The following is not a good error message:

Exception in thread "main" java.lang.IllegalArgumentException: Call to clojure.core/ns did not conform to spec:
In: [2] val: ((require [such.vars :as var] [such.immigration :as immigrate]) (require midje.checking.checkers.defining midje.checking.checkers.chatty midje.checking.checkers.simple midje.checking.checkers.combining midje.checking.checkers.collection)) fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input

You left out this next important line too since it points you to exactly the file and line where the error occurs:

, compiling:(such/sequences.clj:1:1) 

spec produces very detailed error messages driven by the specs and the value being validated. I admit that in some cases the output from a spec error (particularly for complicated syntaxes where there are wide alternative fan-outs) is daunting. However, spec error messages are going to be increasingly common for all of us to see and understand and I think it is worth taking the time to slow down and actually read them.

> Call to clojure.core/ns did not conform to spec:
              ^^^^^^^^^^^^^^ <- macro that was passed invalid values 
> In: [2] 
        ^^^ <- the data path in the :args passed to the macro, here, the 2th element is the require clause (ns = 0, such.sequences = 1)

> val: ((require [such.vars :as var] ...)
         ^^ <- the remaining part of the value that did not match (it has already matched or "consumed" the first two elements successfully)

> fails at: [:args]
               ^^^^^^ <- the path in the ns fdef spec to the failure point

> predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  
                    ^^^etc -> the remaining part of the spec it was trying to match

> Extra input
  specs way of saying that it found something (the val above) but it wasn't what the spec expected next


I'm not trying to pretend this is as easy to digest as an error message that would be produced by hand-written validation and error code, but it's also notoriously difficult to cover all possible cases (which is why the Clojure DSLs have so many error gaps despite having a lot of that code). We are looking to decrease the amount of custom error detection and reporting, so anything we do has to be something we can do generically.

For the specific case of macroexpanded error reporting, I think there *are* more things we can do here (generically) that will improve readability. We *know* we are in the context of checking an fdef spec on a macro, so some of the ":args" stuff is maybe not necessary. Having the val and predicate for a s/cat forwarded to the point of mismatch is both great (as it's specific) but also confusing (because parts of both the input and the cat spec) are missing which removes important context. I think there are ways to indicate that's happening better. Earlier versions of this also reported the full args and we ended up removing that because in many cases it feels redundant (or is potentially large). Maybe there is some heuristic we could follow on when that would help. 
 
- It would be better to say “`require` should be a keyword, not a symbol” than "fails at: [:args] predicate: (cat :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses),  Extra input”

- Suggest Clojure.spec error messages follow the “inverted pyramid” structure of news reports: https://en.wikipedia.org/wiki/Inverted_pyramid That would mean the text message about the error would come first, the spec second, and the wrong expression last. 

- It would be better to name the namespace that has the problem.

As mentioned above, the next line that you omitted points to the file and line with the problem (I went to some trouble to ensure that was reported properly from the point of use).
 
- The stack trace adds nothing to the error. If anything, it makes it less understandable, as the sheer amount of text is offputting.

I agree, but this is to some degree an orthogonal issue. Tools (including the base REPL) choose what to do when they receive an exception from the compiler.  I would like to think more carefully about what the compiler is producing (informationally) and also how tools should be expected to handle these errors. Certainly in cases like this, the stack trace is unnecessary and that's true of many compiler errors. That is a fixable problem and I am interested in making improvements in this area still in 1.9.
 
My https://github.com/marick/structural-typing does (a small) part of what clojure.spec does. I went to a lot of effort to get it to produce good error messages, and it turned out OK. I doubt it would be applicable to clojure.spec, but I’d be happy to sign over any code that could be of use. 

Thanks, I doubt there's much we could use directly.
 
It’s unfortunate that reporting errors is so much harder than detecting them. 

Indeed. Unfortunately errors are read by people and people are hard. :) 

--
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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Alex Miller

unread,
Aug 25, 2016, 9:31:58 PM8/25/16
to Clojure


On Thursday, August 25, 2016 at 8:00:37 PM UTC-5, Rick Moynihan wrote:
I think one obvious area that specs error messages could be improved is with some basic formatting and cosmetic changes. If spec presented errors not as a wall of text and syntax but with some simple formatting it would make a big difference to legibility.

Thanks for your input, I've reached many similar conclusions and we're working on it.

Colin Fleming

unread,
Aug 25, 2016, 10:03:05 PM8/25/16
to clo...@googlegroups.com
Thanks, Adrian. I'm unsure about the disrespectful part - as I mentioned, discussions around community problems are always difficult, but they are important. As with all internet conversations, of course, tone is everything.

But since this is very well-trodden ground, and for whatever reason it's clear it won't change, I definitely agree they're unproductive. I'll be leaving the non-error-message aspects of this conversation alone now.


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Colin Fleming

unread,
Aug 25, 2016, 10:11:39 PM8/25/16
to clo...@googlegroups.com
Hi Rick,

That looks really excellent, and is a huge improvement. Particularly in combination with Leon's proposed change which more precisely identifies the likely failing part of the grammar, this looks like a big win for not much extra effort.

One thing that I think would help a lot would be if it were possible to show the actual text from the failing expression rather than pretty printing a seq representation of it. This would mean modifying the reader such that it either caches the program text from each top-level form as it reads it, or perhaps re-reading the file on an error. This means the relevant code is likely to look more familiar to the user, and also avoids any need to pretty print. Pretty printing is likely to be complicated since it normally works top-down, and uses the type of each form to decide how to lay its sub-parts out. If you're only pretty-printing a fragment from within, say, a large ns form, pprint is unlikely to format it as the user would expect.

Cheers,
Colin

Alex Miller

unread,
Aug 26, 2016, 4:24:40 AM8/26/16
to Clojure
On Thursday, August 25, 2016 at 9:11:39 PM UTC-5, Colin Fleming wrote:

One thing that I think would help a lot would be if it were possible to show the actual text from the failing expression rather than pretty printing a seq representation of it. This would mean modifying the reader such that it either caches the program text from each top-level form as it reads it, or perhaps re-reading the file on an error. This means the relevant code is likely to look more familiar to the user, and also avoids any need to pretty print. Pretty printing is likely to be complicated since it normally works top-down, and uses the type of each form to decide how to lay its sub-parts out. If you're only pretty-printing a fragment from within, say, a large ns form, pprint is unlikely to format it as the user would expect.

Does the caching or file re-reading have any hope of working when you have nested macro expansions (like when-let -> when -> if)? The input to when in that case is a form constructed by when-let, not text sitting in a file. But maybe that doesn't matter if it's still useful for some good percentage of macro calls that have access to text. One benefit of using pprint is that you (the user) can control stuff like *print-length*, *print-suppress-namespace*, *print-right-margin*, etc.

Rick Moynihan

unread,
Aug 26, 2016, 5:15:58 AM8/26/16
to Clojure
On 26 August 2016 at 03:11, Colin Fleming <colin.ma...@gmail.com> wrote:
Hi Rick,

That looks really excellent, and is a huge improvement. Particularly in combination with Leon's proposed change which more precisely identifies the likely failing part of the grammar, this looks like a big win for not much extra effort.

Well it was really just 5-10 minutes work.  I think it shows though that specs errors are actually better than a lot of people are giving them credit for.  Once I'd tidied it up a bit it surprised me.
 
One thing that I think would help a lot would be if it were possible to show the actual text from the failing expression rather than pretty printing a seq representation of it. This would mean modifying the reader such that it either caches the program text from each top-level form as it reads it, or perhaps re-reading the file on an error. This means the relevant code is likely to look more familiar to the user, and also avoids any need to pretty print. Pretty printing is likely to be complicated since it normally works top-down, and uses the type of each form to decide how to lay its sub-parts out. If you're only pretty-printing a fragment from within, say, a large ns form, pprint is unlikely to format it as the user would expect.

Yes, as I was re-rendering the error message it did occur to me that you could do lots more to make it even nicer.  Capturing the source text is certainly one, though mightn't there be a risk of using a large amount of memory to store those metadata strings, especially if they're nested, overlapping sexps. 

Re-reading the file on error would presumably only work if the file was available, if it was only aot'd you'd have to either have captured the source text at macro expansion time (as mentioned above) or try to lookup the source for an even better error - and if it's not found fallback to a pretty-pinting of the form.

I didn't want to go too far down the road with the example, as I wanted to show how much better the message could be with just a modest amount of work.  The main ideas of the proposal are also independent and don't rely too much on each other.

I'm curious whether the core team plan for the formatting of these strings to be a contract; or whether after 1.9.0 is released if they could be flagged as experimental - with further improvements to the rendering being pushed into future clojure releases?

R.

p.s. Colin, just wanted to say a massive thanks for Cursive!  I'm an Emacs Cider user myself, but it's really helped many members of our team, and I've been so impressed with the progress, that I even asked my boss to buy me a copy... I think it'll still take quite a lot to get me off Emacs/Cider; but you might well make it! :-)

Colin Fleming

unread,
Aug 26, 2016, 5:16:20 AM8/26/16
to clo...@googlegroups.com
I'm not sure about that - I suspect it would still be useful even just for surface forms, although it's probably not ideal to have two different modes for when you have the data or not. I had assumed that, assuming that most macro forms are spec'ed, most syntax problems would be encountered by the top-level macro spec. However thinking about it, in the case you mentioned the condition is probably passed through the two macros untouched and a problem there would be encountered by 'if'. However in that case the original text would still be useful since it would not have been touched during the expansions, the problem would be detecting that the form hasn't been modified. Similarly, the 'then' expression of the 'if' would be the original forms from the when-let body wrapped in a do. 

I'm not sure how useful this idea would be in practice, it would need some experimentation to see how well it works.

--

Colin Fleming

unread,
Aug 26, 2016, 5:32:12 AM8/26/16
to clo...@googlegroups.com
I agree that tidied up the error messages are much more understandable. Replacing things like "path" with a description of what it means goes a long way. My main issue with the original error which persists in your version is that the failing predicate really doesn't help much identifying the problem. However Leon's investigations hopefully will help to make that better by more precisely identifying the failing predicate.

I'd envisioned the source text for a particular top-level form only being held for as long as required to eval or compile the form, not being stored permanently in metadata. But I'd have to go and look at the code to see if that's feasible or not. AOTing would not be a problem since the macroexpansion errors would be found when the form was AOTed, those errors would never occur at runtime.

Thanks for the kind words about Cursive too, one of my main goals is to make Clojure more approachable (hence the obsession with error messages), so I'm glad it's helped your co-workers! And you never know, maybe you'll like it enough one day to switch :-)

Rick Moynihan

unread,
Aug 26, 2016, 8:28:30 AM8/26/16
to Clojure
On 26 August 2016 at 10:31, Colin Fleming <colin.ma...@gmail.com> wrote:
I agree that tidied up the error messages are much more understandable. Replacing things like "path" with a description of what it means goes a long way. My main issue with the original error which persists in your version is that the failing predicate really doesn't help much identifying the problem. However Leon's investigations hopefully will help to make that better by more precisely identifying the failing predicate.

Yes the failing predicate isn't always very enlightening, and something like Leon's suggestion seems to match my intuitions about being more accurate, more frequently - though I'll leave judgement on that to people who know this stuff much better than I.
 
I'd envisioned the source text for a particular top-level form only being held for as long as required to eval or compile the form, not being stored permanently in metadata.

Yes, this occurred to me only after reading your response to Alex.  That could be pretty cool, but might require extra specialisation for the macro case.
 
But I'd have to go and look at the code to see if that's feasible or not. AOTing would not be a problem since the macroexpansion errors would be found when the form was AOTed, those errors would never occur at runtime.

Sounds feasible then...

Thanks for the kind words about Cursive too, one of my main goals is to make Clojure more approachable (hence the obsession with error messages), so I'm glad it's helped your co-workers! And you never know, maybe you'll like it enough one day to switch :-)

Well the biggest barrier to me trying to use it more are the lack of Emacs & CIDER & Emacs paredit style keybindings.  I'm not sure if you can easily share those configs with intellij; but if there was a config that had all that together I'd probably be able to last more than 5 minutes without getting frustrated... A discussion for another thread perhaps...

R.
Reply all
Reply to author
Forward
0 new messages