Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by

I'm wondering if there is a notion similar to cut! in prolog that would prevent backtracking. Consider the following specs:

  (s/def ::nested (s/keys :req-un [::a]))
  (s/def ::a (s/keys :req-un [::b]))
  (s/def ::b (s/keys :req-un [::c]))
  (s/def ::c int?)

  (s/def ::top-level (s/keys :req-un [::x]))
  (s/def ::x int?)

  (s/def ::thing (s/or :nested ::nested :top-level ::top-level))
  (s/valid? ::thing {:x 3}) ; true
  (s/valid? ::thing {:a {:b {:c 3}}}) ;true
  (s/valid? ::thing {:a {:b {:c "not int"}}}) ;false
  (s/explain ::thing {:a {:b {:c "not int"}}}) ;neither toplevel nor nested

Here {:a {:b {:c "not int"}}} is clearly neither valid according to ::nested or ::toplevel but once you are checking ::c It's clearly a malformed ::nested and not a malformed ::toplevel.

If this construct does not exist is this something that might be considered in spec 2 or just not something desired?

1 Answer

+1 vote
selected by
Best answer

The result you'll get in Clojure 1.10.1 is:

user=> (s/explain ::thing {:a {:b {:c "not int"}}})
"not int" - failed: int? in: [:a :b :c] at: [:nested :a :b :c] spec: :user/c
{:a {:b {:c "not int"}}} - failed: (contains? % :x) at: [:top-level] spec: :user/top-level

The way this is implemented, it's really matching both in parallel, so it gives you all the failing paths. The explain message is sorted based on path length, so you are intentionally seeing the one that matched "deepest" first.

I don't think it's likely that there will be a "cut" type thing, but there are many possible changes in problem reporting. The particular case of fanout (particularly wide fanout, which is not this case) is one that I think is of higher priority, but we don't have any active plans to work on this in the short term.