Journal 2019.3 - spec, clojure.main errors


I spent almost the entire week completing the large chunk of refactoring I’ve been doing in spec-alpha2. At this point, I think everything is put back together. I’ve updated the spec-alpha2 readme to include some information on using the lib as a git dep, namespace changes, and a summary of things you might find that are different.

At this point, there’s not many visible advantages for a spec user yet. Most of the changes are in the implementation and will affect those either implementing their own specs or wanting to do spec creation without using the spec op macros.

For the latter use case, more dynamic spec uses often ran into issues wrapping the spec op macros to do something like use a set to generate a s/keys spec. Doing so required use of wrapper macros, eval, etc. You can now use the new entry point s/spec*, a function, to generate a spec from data. spec* takes a spec op form, which must use fully-qualified symbols (or you can use the new explicate function to qualify a form).

(require '[clojure.spec-alpha2 :as s])

(defn make-keys-spec [keys]
  (s/spec* (list `s/keys :req (vec keys))))

(s/def ::a int?)
(s/def ::b keyword?) 
(s/explain (make-keys-spec #{::a ::b}) {::a 1})
;; #:user{:a 1} - failed: (contains? % :user/b) 

This is a new functional data interface below the op form macros, which takes data, primarily in the form oflists and symbols.

Additionally, we are starting to formalize what it means to implement your own custom specs. Most of that has existed, but it hasn’t been something we were willing to commit to but we’re moving in that direction. The Spec protocol has been pulled out to a new clojure.spec-alpha2.protocols namespace and implementing a new spec consists of two parts. First, creating a spec op macro that explicates (fully qualifies) the form, and then emits a call to the spec* functional interface. Second, you must provide an implementation of the create-spec multimethod, which is a routing layer under spec* that takes the form and returns an implementation of the Spec protocol. I’m not going to go through the details of this, but everything in spec-alpha2 is now implemented this way so there are copious examples.

I’ll be continuing to work on spec next week, but not sure exactly what yet!

clojure.main errors

This is actually from two weeks ago, but I forgot it last week and it was important. A new user on Slack reported a somewhat messy error message and I suggested that they should move to Clojure 1.10 to get the benefit of the new error reporting. But they were already on 1.10! I dug in a bit more.

The errors were being reported from a ring server started under lein. As I examined the stack trace, it became clear that clojure.main was being used as the main runner by Leiningen to run the ring server. And here’s the important part - we did a ton of work to better report errors from the clojure.main repl, but we did not extend that work to clojure.main -m, -e, etc modes!

I feel dumb for missing this in 1.10. This affects lots of ways that people interact with Clojure, even through other tools like Leiningen. For example, when you are AOT compiling source is being read and you can encounter spec macro errors, but you’ll still get old Clojure 1.9 style errors and a big (useless) stack dump, rather than all the stuff we did for the REPL.

Anyhow, I wrote this up at CLJ-2463 and even hacked up a proof of concept patch and this will be a high priority for 1.11. My big questions are around the stack trace - I think in read/compile/macroexpand cases this (usually) has little value and could be omitted, but some users (language implementors or macro writers) may occasionally want that stack to debug something, so maybe that could have an override switch. Also, we are currently printing just the Clojure file name, without the path, in the error message. With a full Java stack trace, there’s more info there, but with just the file name (“core.clj”), we’ve left out critical context. So, need to think through these questions a bit more, but overall this is a small change with a big impact.

Clojure Survey

One last reminder! The 2019 State of Clojure survey is open until Tues Jan 22nd. Please complete it if you use Clojure!

Non Clojure stuff I enjoyed this week…

Here’s two instrumental things I ran into this week and enjoyed. First, shibyua (ft San Holo) by Covet, propelled by guitarist Yvette Young using tapping and open tunings. Here’s also a video of just her doing this song.

Second, something a bit more raucous - Joy on Fire and their submission for Tiny Desk. I dig their sound - drums, bass, and sax.

Written on January 18, 2019