Future of spec/explain-data, spec/and, etc.

203 views
Skip to first unread message

Mars0i

unread,
Jul 24, 2016, 11:40:41 PM7/24/16
to Clojure
spec/explain-data seems very important.  It allows programmatic responses to spec failures.  Maybe explain-data's behavior hasn't yet stabilized, though?  The structure of the return value has changed between 1.9.0-alpha7 to the current 1.9.0-alpha10, the docstring is a bit vague, and the Spec Guide only talks about it very briefly.

At present, it's easy to figure out which test(s) has/have failed by examining the :path value(s) in explain-data's return value in some situations, such as when specs are combined using spec/keys or spec/or. In other situations--at least when specs are combined with spec/and,  the:path values are empty.  Unlike spec/or, there's no way to specify keywords that would identify the failed test.

Am I right that explain-data is in flux?  Is the goal that in the future, it will always be possible for developers to specify composite specs in such a way that explain-data can return info that identifies the failed test clearly?  For example, in the first spec/and illustration below, maybe explain-data could use the names of the component specs as path elements?  (Or am I just confused about something?)

Thanks-

Example, using Clojure 1.9.0-alpha10:

(s/def ::even even?)
(s/def ::zero-to-ten (s/int-in 0 10)) ; require number from 0 to 10 inclusive

user=> (s/explain-data (s/or :pred1 ::even :pred2 ::zero-to-ten) 11)
{:clojure.spec/problems
 ({:path [:pred1], :pred even?, :val 11, :via [:user/even], :in []}
  {:path [:pred2],
   :pred (int-in-range? 0 10 %),
   :val 11,
   :via [:user/zero-to-ten],
   :in []})}

;; Note that the format of the path entries are different above and below.
;; Is there a reason for this difference, or will later versions return
;; the same path elements?

user=> (s/explain-data (s/keys :req-un [::even ::zero-to-ten]) {:even 11 :zero-to-ten 11})
{:clojure.spec/problems
 ({:path [:even], :pred even?, :val 11, :via [:user/even], :in [:even]}
  {:path [:zero-to-ten],
   :pred (int-in-range? 0 10 %),
   :val 11,
   :via [:user/zero-to-ten],
   :in [:zero-to-ten]})}

;; Here there's nothing in the :path or :in sequences, although :via provides some information:
user=> (s/explain-data (s/and ::even ::zero-to-ten) 11)
#:clojure.spec{:problems [{:path [], :pred even?, :val 11, :via [:user/even], :in []}]}

;; Note that only the first failed test is identified, which makes sense.

;; Another s/and example, with no info other than the value of :pred to indicate what test failed:
user=> (s/explain-data (s/and even? (s/int-in 0 10)) 11)
#:clojure.spec{:problems [{:path [], :pred even?, :val 11, :via [], :in []}]}

Alex Miller

unread,
Jul 25, 2016, 1:34:23 AM7/25/16
to Clojure


On Sunday, July 24, 2016 at 10:40:41 PM UTC-5, Mars0i wrote:
spec/explain-data seems very important.  It allows programmatic responses to spec failures.  Maybe explain-data's behavior hasn't yet stabilized, though?  The structure of the return value has changed between 1.9.0-alpha7 to the current 1.9.0-alpha10, the docstring is a bit vague, and the Spec Guide only talks about it very briefly.

explain-data is not in flux but as we are in alpha, it could still change.

:path are path tags
:via are specs
:in are data keys

At present, it's easy to figure out which test(s) has/have failed by examining the :path value(s) in explain-data's return value in some situations, such as when specs are combined using spec/keys or spec/or. In other situations--at least when specs are combined with spec/and,  the:path values are empty.  Unlike spec/or, there's no way to specify keywords that would identify the failed test.

Am I right that explain-data is in flux?  Is the goal that in the future, it will always be possible for developers to specify composite specs in such a way that explain-data can return info that identifies the failed test clearly?  For example, in the first spec/and illustration below, maybe explain-data could use the names of the component specs as path elements?  (Or am I just confused about something?)

As specs, the component spec path is recorded in :via.
 

Thanks-

Example, using Clojure 1.9.0-alpha10:

(s/def ::even even?)
(s/def ::zero-to-ten (s/int-in 0 10)) ; require number from 0 to 10 inclusive

user=> (s/explain-data (s/or :pred1 ::even :pred2 ::zero-to-ten) 11)
{:clojure.spec/problems
 ({:path [:pred1], :pred even?, :val 11, :via [:user/even], :in []}
  {:path [:pred2],
   :pred (int-in-range? 0 10 %),
   :val 11,
   :via [:user/zero-to-ten],
   :in []})}

;; Note that the format of the path entries are different above and below.
;; Is there a reason for this difference, or will later versions return
;; the same path elements?

Both examples seem consistent with my prior description of the data (specs in :via, paths in :path, and data keys in :in). They are specs with different structure so I would not expect them to yield the same explain results.
 
user=> (s/explain-data (s/keys :req-un [::even ::zero-to-ten]) {:even 11 :zero-to-ten 11})
{:clojure.spec/problems
 ({:path [:even], :pred even?, :val 11, :via [:user/even], :in [:even]}
  {:path [:zero-to-ten],
   :pred (int-in-range? 0 10 %),
   :val 11,
   :via [:user/zero-to-ten],
   :in [:zero-to-ten]})}

;; Here there's nothing in the :path or :in sequences, although :via provides some information:

Yes, as expected.
 
user=> (s/explain-data (s/and ::even ::zero-to-ten) 11)
#:clojure.spec{:problems [{:path [], :pred even?, :val 11, :via [:user/even], :in []}]}

;; Note that only the first failed test is identified, which makes sense.
 

;; Another s/and example, with no info other than the value of :pred to indicate what test failed:
 
What other info could be provided? You have the predicate and the invalid value. If you had named the predicate, you would have more info.

user=> (s/explain-data (s/and even? (s/int-in 0 10)) 11)
#:clojure.spec{:problems [{:path [], :pred even?, :val 11, :via [], :in []}]}

user=> (s/def ::even even?)
:user/even
user=> (s/def ::irange (s/int-in 0 10))
:user/irange
user=> (s/explain-data (s/and ::even ::irange) 11)

Mars0i

unread,
Jul 25, 2016, 11:56:09 AM7/25/16
to Clojure
Alex, thanks very much.  That's all very helpful.  Just what I needed.  I hadn't seen an explanation of the meaning of :path, :via, and :in, and was guessing about them from experiments.    :via and :in aren't mentioned in the docstring, so I wasn't sure whether to depend on them.   (The source code that feeds into explain-data isn't easy reading--no reason to think it would be--and I haven't made sense of it so far.)   I just noticed this morning that :path is explained in "clojure.spec - Rationale and Overview".

I assume that the explain-data docstring will eventually sketch the meaning of :path, :via, and :in.  Or is this the sort of thing that I ought to file a JIRA ticket on if I think it's important? (I'm new to using JIRA don't want to clutter up with irrelevant tickets during an alpha process.  I don't see a ticket about the explain-data docstring.

Mars0i

unread,
Jul 25, 2016, 12:04:32 PM7/25/16
to Clojure
On Monday, July 25, 2016 at 10:56:09 AM UTC-5, Mars0i wrote:
 just noticed this morning that :path is explained in "clojure.spec - Rationale and Overview".

There's also material about :path in your Spec Guide, but I had not fully understood it.  I get it now--at least up to the level of my understanding of the rest of spec.
Reply all
Reply to author
Forward
0 new messages