Fork me on GitHub

Is there a way to make the spec optional and star operators greedy?


I have the following spec:

(s/cat :binding (s/? (s/cat :binding-var ::variable-name
                            :sep #{'<-}))
       :fact-type any?
       :destructured-fact (s/? ::destructured)
       :expressions (s/* any?))


This is horribly ambiguous as written, due to the use of any? and repeated/optional elements.


I’d like to make the first s/? greedy, so to say if it’s possible to match the :binding part then that’s what should happen.


The only alternative I can think of is to do this, taking advantage of ordering precedence for alternates:

  :with-binding (s/cat :binding-var ::variable-name
                       :sep #{'<-}
                       :fact-type any?
                       :destructured-fact (s/? ::destructured)
                       :expressions (s/* any?))
  :no-binding (s/cat :fact-type any?
                     :destructured-fact (s/? ::destructured)
                     :expressions (s/* any?)))


But that’s kind of ugly.


Is there any defined precedence of optional elements inside an s/cat?

Alex Miller (Clojure team)04:01:14

In short, no. The way the regex derivative stuff works is kind of doing all the options in parallel


FWIW other regex parallel implementations allow this even though they work in lockstep with no backtracking.


e.g. NFAs or VM approaches


I think it would be worth specifying in the doc if ops like ? and * are greedy or not - it’s not mentioned right now and is useful information in ambiguous cases.

Alex Miller (Clojure team)04:01:34

I don’t know that I understand what you’re trying to spec well enough but the latter seems more readable to me

Alex Miller (Clojure team)04:01:45

One thing I’ve found is that spec’ing a dsl is a pretty good way to judge how good your dsl is :)


This is for Clara Rules


So I can’t easily modify how it works.


> One thing I’ve found is that spec’ing a dsl is a pretty good way to judge how good your dsl is :) Yeah, in the case of Clara, there is a bit too much ambiguity in this case I’d say. Have to figure out a way to tighten it down a bit perhaps. Spec to the rescue.


I seem to recall that someone published a repository with some specs for core functions (beyond what is currently included in, but now I can’t find it. Anyone else remember this?


I realize that these specs may be incomplete or buggy, and that’s OK for my use case. This is just for toying around with instrumentation.


Oh, nevermind, my fourth google search finally found it


There's also this one: Don't know how it compares to the one you mentioned

Alex Miller (Clojure team)19:01:19

the repos I’ve looked at were both mostly wrong and not very good examples of specs, so ymmv


@alexmiller Good to know. I’m not that concerned about accuracy for this case, but if they are not well-written specs, that’s more of an issue, since I’m using these as a temporary proxy for the real clojure.core specs


I should clarify: I’m not worried about completeness in the sense that if, say, some functions are missing specs for certain less common arities, that’s OK. If the specs are just buggy in general then that would cause an issue.

Alex Miller (Clojure team)19:01:31

from what I’ve looked at, the specs are wrong, as in will both allow bad things and reject good things

Alex Miller (Clojure team)19:01:22

and most of them spec a bunch of things that instrument won’t work with (inline functions and functions with primitive args), which is not bad, but also not useful


Gotcha. Yeah, for my case, rejecting good things will cause issues. I’ll take a look.


> bunch of things that instrument won’t work with (inline functions and functions with primitive args) I’m afraid I don’t understand. Can you expand on this a little?

Alex Miller (Clojure team)19:01:23

instrument currently doesn’t work with: functions taking/returning primitives (fixable), inlines, multimethods (fixable), and protocols

Alex Miller (Clojure team)19:01:44

so spec’ing + will buy you nothing for example

Alex Miller (Clojure team)19:01:32

but it’s not harmful and does get included in docs, which might be valuable in and of itself


@seancorfield @alexmiller Ah, thanks. I did not know about that. If this works, I’ll be using instrumentation in CLJS, so presumably the primitive issue will be different

Alex Miller (Clojure team)20:01:17

Oh yeah, I’m talking Clojure


Well, I didn’t mention the CLJS part before 🙂 and in any case, those specs are in CLJ, so I can see why you thought CLJ (I’ll need to copy them into a cljs file if I want to use them).


Why is this valid?

(s/def ::cat (s/cat :a keyword? :b any?))
(s/def ::foo (s/coll-of (s/spec ::cat)))
(s/valid? ::foo {:a "a"})
=> true


@kenny because

(coll? {})
;; => true


@kenny I think because a map is a coll. Presumably s/coll-of calls seq on the map to get at the elements, which in this case are entries. Then s/cat calls seq and gets the entry key and val.


Shit, that always gets me.


Thanks guys.