clojure.spec

2901 views
Skip to first unread message

Rich Hickey

unread,
May 23, 2016, 10:12:29 AM5/23/16
to clo...@googlegroups.com
Introducing clojure.spec

I'm happy to introduce today clojure.spec, a new core library and support for data and function specifications in Clojure.

Better communication

Clojure is a dynamic language, and thus far we have relied on documentation or external libraries to explain the use and behavior of functions and libraries. But documentation is difficult to produce, is frequently not maintained, cannot be automatically checked and varies greatly in quality. Specs are expressive and precise. Including spec in Clojure creates a lingua franca with which we can state how our programs work and how to use them.

More leverage and power

A key advantage of specifications over documentation is the leverage they provide. In particular, specs can be utilized by programs in ways that docs cannot. Defining specs takes effort, and spec aims to maximize the return you get from making that effort. spec gives you tools for leveraging specs in documentation, validation, error reporting, destructuring, instrumentation, test-data generation and generative testing.

Improved developer experience

Error messages from macros are a perennial challenge for new (and experienced) users of Clojure. specs can be used to conform data in macros instead of using a custom parser. And Clojure's macro expansion will automatically use specs, when present, to explain errors to users. This should result in a greatly improved experience for users when errors occur.

More robust software

Clojure has always been about simplifying the development of robust software. In all languages, dynamic or not, tests are essential to quality - too many critical properties are not captured by common type systems. spec has been designed from the ground up to directly support generative testing via test.check https://github.com/clojure/test.check. When you use spec you get generative tests for free.

Taken together, I think the features of spec demonstrate the ongoing advantages of a powerful dynamic language like Clojure for building robust software - superior expressivity, instrumentation-enhanced REPL-driven development, sophisticated testing and more flexible systems. I encourage you to read the spec rationale and overview http://clojure.org/about/spec. Look for spec's inclusion in the next alpha release of Clojure, within a day or so.

Note that spec is still alpha, and some details are likely to change. Feedback welcome.

I hope you find spec useful and powerful!

Rich

adrian...@mail.yu.edu

unread,
May 23, 2016, 10:48:52 AM5/23/16
to Clojure
This looks incredible and it sounds like something which could immediately be put to good use in both hobby and production projects. Excited to test it out when the alpha is available. Thank you!

Alan Thompson

unread,
May 23, 2016, 12:04:43 PM5/23/16
to clo...@googlegroups.com
Looks great - eagerly awaiting a chance to use it.
Alan


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

Andrey Grin

unread,
May 23, 2016, 12:13:24 PM5/23/16
to Clojure
 Is it planned to support recursive definitions? Example from.plumatic schema:
 
(def BinaryTree 
  (maybe ;; any empty binary tree is represented by nil
   {:value long 
    :left (recursive #'BinaryTree) 
    :right (recursive #'BinaryTree)}))

Alex Miller

unread,
May 23, 2016, 12:16:44 PM5/23/16
to Clojure
Yes, you can create recursive definitions by registering a spec that refers to itself via registered name (a namespaced keyword).

Sean Corfield

unread,
May 23, 2016, 1:51:18 PM5/23/16
to Clojure Mailing List
Awesome! I know one company that’s going to jump straight on 1.9.0-alpha1! ☺

Heck, we may even start today with 1.9.0-master-SNAPSHOT since clojure.spec was committed a couple of hours ago ☺

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

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

Nicola Mometto

unread,
May 23, 2016, 2:06:25 PM5/23/16
to clo...@googlegroups.com
Some minor feedback while reading through `clojure.spec`:

`clojure.spec/->sym` has been implemented in a significant number of libraries already (see: https://crossclj.info/clojure/var->sym.html), I propose we include this in `clojure.core` under the name of `var->sym`
signature.asc

scott stackelhouse

unread,
May 23, 2016, 3:08:49 PM5/23/16
to Clojure
Could someone describe what a spec of a seq in a seq would look like?  i.e. ['("a" "b" "c") '("d" "e" "f")].  I'm not quite "getting it."

--Scott

Alex Miller

unread,
May 23, 2016, 3:28:32 PM5/23/16
to Clojure
Yeah, nested regexes are one of the places people are most likely to be tripped up. 

One of those inner lists could be speced as:

(def string-list (s/* string?)) 

;; or as (s/coll-of string?) - not a strong preference in this particular case but there are tradeoffs

And then the outer list is something like this:

(def outer (s/* (s/spec string-list))

where the s/spec is the key thing - that creates a new "sequential context". Otherwise, the string-list regex ops become part of the outer regex ops.

One difference here will be that the spec above will conform the string-list to a vector (as all sequential things conform to a vector). The coll-of approach would give you control over that though:

(s/conform (s/* (s/coll-of string? ())) ['("a" "b" "c") '("d" "e" "f")])
=> [("a" "b" "c") ("d" "e" "f")]

The benefit of using s/* in string-list is that if you wanted to include string-list inside another regex you could but with coll-of, it would always start a new collection:

(s/conform (s/cat :num integer? :strs string-list) [100 "a" "b"])
=> {:num 100, :strs ["a" "b"]}

So, tradeoffs.

Alex Miller

unread,
May 23, 2016, 3:41:01 PM5/23/16
to Clojure
That schema looks weird to me as it seems to under-constrain leaf vs branch so this is not really equivalent but makes sense to me:

(spec/def ::tree (spec/or :leaf ::leaf :branch ::branch))
(spec/def ::leaf integer?)
(spec/def ::branch (spec/cat :left ::tree :right ::tree))
(spec/conform ::tree [[1 2] [3 4]])
=> [:branch {:left [:branch {:left [:leaf 1], :right [:leaf 2]}], :right [:branch {:left [:leaf 3], :right [:leaf 4]}]}]

Because you register specs in the registry by keyword that gives you the point of indirection to do recursive structures.

Generate some trees!

(gen/sample (spec/gen ::tree) 5)
=> (-1 -1 (0 0) (0 2) (-1 -1))

(gen/sample (spec/gen ::tree) 5)
=> (((-1 0) 0) 0 1 (0 1) (1 -3))

scott stackelhouse

unread,
May 23, 2016, 3:54:37 PM5/23/16
to Clojure
Awesome, thanks!  I had misunderstood where the idea of a sequence was created (the sequential context as you put it).

Andrey Grin

unread,
May 23, 2016, 4:09:46 PM5/23/16
to Clojure
Thanks, Alex. I've taken random example from plumatic.schema wiki to quickly illustrate the question. In my actual use case I dynamically generate schemas based on hundreds of model Java classes (beans) with mutual references so I need recursion (and also some way to model inheritance, that was is achieved with "conditional" in plumatic). Will try to implement the same with spec.

Ivan Reese

unread,
May 23, 2016, 4:11:30 PM5/23/16
to Clojure
Is there anywhere we can read anything about the design process behind clojure.spec? I took a look at dev.clojure.org / JIRA, but I haven't yet found anything on the topic.

Alex Miller

unread,
May 23, 2016, 4:34:18 PM5/23/16
to Clojure
clojure.spec has been developed internally for the last couple months. It combines a lot of different threads of thought. What are looking for?

There will be some more usage docs coming soon.

Alex Miller

unread,
May 23, 2016, 4:38:24 PM5/23/16
to Clojure
I think you could apply the same idea - use a regular translation between Java class and registered name and then always refer to the registered name.

There are probably multiple approaches to modeling inheritance, so hard to recommend something without knowing more. In general, attributes coming from super classes can be modeled using the parent attribute name. And I would recommend looking into multi-spec for doing validation of a number of "types" that could be flowing into the same location by leveraging a multimethod that conditionally provides the spec to use.

Rich Hickey

unread,
May 23, 2016, 5:20:36 PM5/23/16
to clo...@googlegroups.com
I did most of the design of spec in a (paper) notebook.

The rationale tries to capture the salient driving forces.

If there is a specific question you have I’d be happy to answer.

Rich

> On May 23, 2016, at 4:11 PM, Ivan Reese <ivan...@gmail.com> wrote:
>
> Is there anywhere we can read anything about the design process behind clojure.spec? I took a look at dev.clojure.org / JIRA, but I haven't yet found anything on the topic.
>

Andrey Grin

unread,
May 23, 2016, 5:20:38 PM5/23/16
to Clojure
Thank you. Yes, it seems that for recursion I can use the same approach. As for inheritance currently I just include all parent attributes in every child schema. As for spec definition multi-spec, as I understand it, requires "defmethod" for every class which is probably a problem for my case since all inheritance hierarchies are also determined at run-time. I will need to find some approach that is both dynamic and supports precise error messages in case validation fails.

Ambrose Bonnaire-Sergeant

unread,
May 23, 2016, 5:26:31 PM5/23/16
to clojure
Thanks Rich+team, this is awesome.

Instrumented vars via `fdef` do not seem to add :doc metadata yet
(which is advertised in the docstring for `fdef`).

Am I missing something?

Thanks,
Ambrose

Alex Miller

unread,
May 23, 2016, 5:27:10 PM5/23/16
to Clojure
You can dynamically install defmethods at runtime too so you could catch this problem when it falls into the default, generate the schema, register it, then re-invoke. Theoretically.

Rich Hickey

unread,
May 23, 2016, 5:29:46 PM5/23/16
to clo...@googlegroups.com
fdef will not add doc metadata (see rationale re: not putting more stuff in the namespaces/vars), but specs will be present when you call ‘doc’. That doc enhancement was in a push later in the afternoon.

https://github.com/clojure/clojure/commit/4c8efbc42efa22ec1d08a1e9fa5dd25db99766a9

Ambrose Bonnaire-Sergeant

unread,
May 23, 2016, 5:34:28 PM5/23/16
to clojure
I see, thanks.

Ambrose Bonnaire-Sergeant

unread,
May 23, 2016, 5:59:39 PM5/23/16
to clojure
I'm having trouble calling `s/gen`, might be some sort of AOT compilation error.
(I'm guessing this line has something to do with it).

I'm using the latest master-SNAPSHOT.

(ns gen-load.core
  (:require [clojure.spec :as s]))

(s/gen integer?)
;CompilerException java.lang.NoClassDefFoundError: clojure/spec/gen$gen_for_pred, compiling:(gen_load/core.clj:4:1) 

Thanks,
Ambrose

Nicola Mometto

unread,
May 23, 2016, 6:03:22 PM5/23/16
to clo...@googlegroups.com
Possibly CLJ-1544 related?
signature.asc

Nicola Mometto

unread,
May 23, 2016, 6:12:19 PM5/23/16
to clo...@googlegroups.com
Looks like it is, in the meanwhile this patch should fix it: http://sprunge.us/XTiA
signature.asc

Shaun Parker

unread,
May 23, 2016, 6:34:48 PM5/23/16
to Clojure
Thanks to everyone who worked on it! I'm excited there's finally an idiomatic way and can't wait to use it.

Is there any chance the three clojure.spec namespaces will be released as a library that could be use until 1.9.0 is released? It seems like we'd only be missing compile time macro checking/reporting and the updated clojure.repl/doc unless I'm missing something. It'd be nice to start gradually using everything else now.

Ambrose Bonnaire-Sergeant

unread,
May 23, 2016, 6:35:16 PM5/23/16
to clojure
I'm observing mutually recursive regex ops taking a long time to generate
test.check generators. Is this expected?

(s/def ::a (s/nilable (s/cat :a ::a
                             :b ::b
                             :c ::c)))
(s/def ::b (s/nilable (s/cat :a ::a
                             :b ::b
                             :c ::c)))
(s/def ::c (s/nilable (s/cat :a ::a
                             :b ::b
                             :c ::c)))

(time (s/gen ::a))
;"Elapsed time: 3993.431793 msecs"
nil

Thanks,
Ambrose

Sean Corfield

unread,
May 23, 2016, 6:38:03 PM5/23/16
to Clojure Mailing List
On 5/23/16, 2:29 PM, "Rich Hickey" <clo...@googlegroups.com on behalf of richh...@gmail.com> wrote:
>fdef will not add doc metadata (see rationale re: not putting more stuff in the namespaces/vars), but specs will be present when you call ‘doc’. That doc enhancement was in a push later in the afternoon.

Nice:

(require '[clojure.spec :as s])
(s/fdef clojure.core/symbol
:args (s/alt :separate (s/cat :ns string? :n string?)
:str string?
:sym symbol?)
:ret symbol?)

(doc symbol)
-------------------------
clojure.core/symbol
([name] [ns name])
Returns a Symbol with the given namespace and name.
Spec
args: (alt :separate (cat :ns string? :n string?) :str string? :sym symbol?)
ret: symbol?

Looks like a small bug in explain tho’ – is it worth filing a JIRA bug yet or wait until the Alpha?

(s/instrument-all)

(symbol 1) ;; this is fine…

clojure.lang.ExceptionInfo: Call to #'clojure.core/symbol did not conform to spec:
At: [:args :separate :ns] val: 1 fails predicate: string?
At: [:args :str] val: 1 fails predicate: string?
At: [:args :sym] val: 1 fails predicate: symbol?
:clojure.spec/args (1)

(symbol "a" :a) ;; this should explain that :a fails predicate: string?

java.lang.IllegalArgumentException: No matching clause: :clojure.spec/accept
clojure.spec/op-explain/invokeStatic spec.clj: 1196
clojure.spec/op-explain/fn spec.clj: 1192
clojure.core/map/fn core.clj: 2657
...
clojure.core/apply/invokeStatic core.clj: 646
clojure.spec/op-explain/invokeStatic spec.clj: 1196
clojure.spec/re-explain/invokeStatic spec.clj: 1259
clojure.spec/regex-spec-impl/reify/explain* spec.clj: 1282
clojure.spec/explain-data*/invokeStatic spec.clj: 143
clojure.spec/spec-checking-fn/conform! spec.clj: 520
clojure.spec/spec-checking-fn/fn spec.clj: 532




George Singer

unread,
May 23, 2016, 6:40:19 PM5/23/16
to clo...@googlegroups.com
Rich Hickey <richhickey <at> gmail.com> writes:

>
> I did most of the design of spec in a (paper) notebook.
>
> The rationale tries to capture the salient driving forces.
>
> If there is a specific question you have I’d be happy to answer.
>
> Rich
>

> > On May 23, 2016, at 4:11 PM, Ivan Reese <ivanreese <at> gmail.com>

wrote:
> >
> > Is there anywhere we can read anything about the design process behind
clojure.spec? I took a look at
> dev.clojure.org / JIRA, but I haven't yet found anything on the topic.
> >
> > --
> > You received this message because you are subscribed to the Google
> > Groups "Clojure" group.

> > To post to this group, send email to clojure <at> 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+unsubscribe <at> 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+unsubscribe <at> googlegroups.com.


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

I'd love to hear Rich give a talk about this (the design process, his
rationale, and so forth) -- especially how it relates to static type
systems. I know that there's write-up already on the clojure website, but
this would be a great talk also.

Can't wait to use this.

Rich Hickey

unread,
May 23, 2016, 6:45:59 PM5/23/16
to clo...@googlegroups.com
That one’s already fixed if you grab the latest.

Rich Hickey

unread,
May 23, 2016, 6:49:37 PM5/23/16
to clo...@googlegroups.com
Currently gens are not lazy, so entire tree is generated. This is because a) test.check isn’t lazy either and b) we want errors when asking for gen, not using it. But it is rough for recursive specs as you see.

For now you can limit the recursion depth to control the branching:

(binding [s/*recursion-limit* 3]
(time (s/gen ::a)))

Sean Corfield

unread,
May 23, 2016, 7:49:31 PM5/23/16
to Clojure Mailing List
On 5/23/16, 3:45 PM, "Rich Hickey" <clo...@googlegroups.com on behalf of richh...@gmail.com> wrote:
>That one’s already fixed if you grab the latest.

Confirmed! Thank you!

Sean



Alan Moore

unread,
May 23, 2016, 8:01:09 PM5/23/16
to Clojure
Your timing couldn't have been better... we have been needing something like this. Thought of building something similar which would only have been an ad hoc, informally specified, bug ridden, slow implementation of half of clojure.spec.

Thank you for spending you hammock time on this important feature/library. Looking forward to using it.

Alan

Leif

unread,
May 23, 2016, 11:42:27 PM5/23/16
to Clojure
Feedback:

The regex quantifiers seem to be greedy.  I suppose that is the standard choice, but it would be nice if the docs explicitly said that.

Maybe have a 'rep' macro for exact quantification?  My use cases for this are short sequences that would be handled just fine by 'cat', so I don't really need it.  But if someone else needs to specify long-ish but fixed-size sequences, or sequences with a specific length range, they should speak up now.

The docs should mention that you need to provide your own test.check dependency.  They do obliquely, but being explicit never hurts.

An example or two under the "Features > Overview > Maps" section would be nice.  The text makes many good points about the design decisions made for map specs, I'd like to see them in action.  (I am skeptical that a short example would convince me to namespace all my damn map keywords, though perhaps you'll lead me down the virtuous path.)

All that said, it looks good and I think I'll have fun trying it out.
--Leif

Alex Miller

unread,
May 23, 2016, 11:54:56 PM5/23/16
to Clojure
Hi Leif, thanks for the feedback!

The use for regex ops is primarily expected to be syntax where greedy is typically a fine default (and where the sequence length is typically not long with exact quantification).

You can use the & operator though to add arbitrary predicates around another regex operator:

;; i1 is greedy here
(def nn (s/cat :i1 (s/+ integer?) :i2 (s/+ integer?)))
(s/conform nn [1 2 3 4 5])
=> {:i1 [1 2 3 4], :i2 [5]}

;; use s/& to bound i1 to length < 3
(def nn' (s/cat :i1 (s/& (s/+ integer?) #(< (count %) 3)) :i2 (s/+ integer?)))
(s/conform nn' [1 2 3 4 5])
=> {:i1 [1 2], :i2 [3 4 5]}

that predicate is arbitrary so could set any occurence operator needed there. In practice I have been using spec every day for weeks and I have only occasionally needed anything like this though.

Re the test.check dependency - this will be made more explicit in docs to come. But fair point.

I have written a lengthy guide with more examples which we'll release tomorrow. The rationale was not meant to serve as either tutorial docs or reference material but rather as ... the rationale. :)

On namespaced keywords, there are a couple language changes also on the way to provide namespaced literal maps (for reading and printing) and namespaced key destructuring. I expect these will also help in reducing the typing burden of namespaced keys.  See CLJ-1910 and CLJ-1919. It's likely these will be included in alpha2.

se...@corfield.org

unread,
May 24, 2016, 1:16:32 AM5/24/16
to Clojure Mailing List

I was a bit puzzled by the :req-un / :opt-un stuff in maps. Am I right that there is a requirement here for the keys to be namespaced but the actual namespace is completely irrelevant / ignored?

 

(defn thing [m]

  (+ (:a m) (:b m) (or (:c m) 1)))

(s/fdef thing

        :args (s/cat :map (s/and (s/keys :req-un [:x/a :y/b]

                                         :opt-un [:z/c])

                                 (s/map-of keyword? number?)))

        :ret  number?)

 

I tried several namespace prefixes here and it seemed I can use anything and they don’t even need to be consistent.

 

Wouldn’t it be easier to just allow un-namespaced keywords here? Or is there some aspect of the namespacing here that I’m missing?

 

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

 

From: Rich Hickey
Sent: Monday, May 23, 2016 7:12 AM
To: clo...@googlegroups.com
Subject: clojure.spec

 

Introducing clojure.spec

 

I'm happy to introduce today clojure.spec, a new core library and support for data and function specifications in Clojure.

 

Better communication

 

Clojure is a dynamic language, and thus far we have relied on documentation or external libraries to explain the use and behavior of functions and libraries. But documentation is difficult to produce, is frequently not maintained, cannot be automatically checked and varies greatly in quality. Specs are expressive and precise. Including spec in Clojure creates a lingua franca with which we can state how our programs work and how to use them.

 

More leverage and power

 

A key advantage of specifications over documentation is the leverage they provide. In particular, specs can be utilized by programs in ways that docs cannot. Defining specs takes effort, and spec aims to maximize the return you get from making that effort. spec gives you tools for leveraging specs in documentation, validation, error reporting, destructuring, instrumentation, test-data generation and generative testing.

 

Improved developer experience

 

Error messages from macros are a perennial challenge for new (and experienced) users of Clojure. specs can be used to conform data in macros instead of using a custom parser. And Clojure's macro expansion will automatically use specs, when present, to explain errors to users. This should result in a greatly improved experience for users when errors occur.

 

More robust software

 

Clojure has always been about simplifying the development of robust software. In all languages, dynamic or not, tests are essential to quality - too many critical properties are not captured by common type systems. spec has been designed from the ground up to directly support generative testing via test.check https://github.com/clojure/test.check. When you use spec you get generative tests for free.

 

Taken together, I think the features of spec demonstrate the ongoing advantages of a powerful dynamic language like Clojure for building robust software - superior expressivity, instrumentation-enhanced REPL-driven development, sophisticated testing and more flexible systems. I encourage you to read the spec rationale and overview  http://clojure.org/about/spec. Look for spec's inclusion in the next alpha release of Clojure, within a day or so.

 

Note that spec is still alpha, and some details are likely to change. Feedback welcome.

 

I hope you find spec useful and powerful!

 

Rich

 

--

Alex Miller

unread,
May 24, 2016, 1:25:26 AM5/24/16
to Clojure


On Tuesday, May 24, 2016 at 12:16:32 AM UTC-5, Sean Corfield wrote:

I was a bit puzzled by the :req-un / :opt-un stuff in maps. Am I right that there is a requirement here for the keys to be namespaced but the actual namespace is completely irrelevant / ignored?


No - the :req-un / :opt-un keys are used for two purposes:
1. The namespaced key is used to locate the spec in the registry
2. The unnamespaced key is used to find the value in the map

Once those are found, the spec is used to validate the attribute value.

 

 

(defn thing [m]

  (+ (:a m) (:b m) (or (:c m) 1)))

(s/fdef thing

        :args (s/cat :map (s/and (s/keys :req-un [:x/a :y/b]

                                         :opt-un [:z/c])

                                 (s/map-of keyword? number?)))

        :ret  number?)

 

I tried several namespace prefixes here and it seemed I can use anything and they don’t even need to be consistent.


Right - if no spec is found then nothing happens. But if you registered a spec as :x/a  or :y/b those specs would be used to validate the arg.

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.

se...@corfield.org

unread,
May 24, 2016, 2:14:09 AM5/24/16
to Clojure

OK, that explains a lot. Thank you. That needs to be clarified on the web page I think…

 

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

 

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.

Atamert Ölçgen

unread,
May 24, 2016, 3:28:11 AM5/24/16
to clo...@googlegroups.com
Isn't s redefined in this example:

user=> (require '[clojure.spec :as s])
(s/def ::even? (s/and integer? even?))
(s/def ::odd? (s/and integer? odd?))
(s/def ::a integer?)
(s/def ::b integer?)
(s/def ::c integer?)
(def s (s/cat :forty-two #{42}
              :odds (s/+ ::odd?)
              :m (s/keys :req-un [::a ::b ::c])
              :oes (s/* (s/cat :o ::odd? :e ::even?))
              :ex (s/alt :odd ::odd? :even ::even?)))
user=> (s/conform s [42 11 13 15 {:a 1 :b 2 :c 3} 1 2 3 42 43 44 11])
{:forty-two 42,
 :odds [11 13 15],
 :m {:a 1, :b 2, :c 3},
 :oes [{:o 1, :e 2} {:o 3, :e 42} {:o 43, :e 44}],
 :ex {:odd 11}}


Kind Regards,
Atamert Ölçgen

◻◼◻
◻◻◼
◼◼◼

www.muhuk.com

Alex Miller

unread,
May 24, 2016, 8:10:48 AM5/24/16
to Clojure
The first use is a namespace alias and the second is a var - they don't overlap in usage.

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.

Atamert Ölçgen

unread,
May 24, 2016, 8:48:33 AM5/24/16
to clo...@googlegroups.com
Thanks Alex! I didn't know this.


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.



--
Kind Regards,
Atamert Ölçgen

◻◼◻
◻◻◼
◼◼◼

www.muhuk.com

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

Steve Miner

unread,
May 24, 2016, 10:05:31 AM5/24/16
to clo...@googlegroups.com

On May 24, 2016, at 8:10 AM, Alex Miller <al...@puredanger.com> wrote:

The first use is a namespace alias and the second is a var - they don't overlap in usage.

Right, but it’s still a potential source of confusion.  The explanation distracts from the point of the example which is to demonstrate the new spec feature.

JvJ

unread,
May 24, 2016, 1:07:22 PM5/24/16
to Clojure
So happy that there's now a standard way to do this!  It sounds like it would help with my latest project.

Unfortunately, my latest project is called "SPECS".... so I guess I'll have to find a different name.

Rich Hickey

unread,
May 24, 2016, 1:25:41 PM5/24/16
to clo...@googlegroups.com
And now, in the alpha release branching gens *are* lazy, so gen calls on recursive specs are fast.

scott stackelhouse

unread,
May 24, 2016, 1:45:06 PM5/24/16
to Clojure
I'm having a problem writing a spec for a map with some required keywords and some optional keywords.  The caveat here is that the optional keywords are all or none... that is they are optional but if one is present they must all be present.

What I tried to write was:

(s/keys :req [::a ::b ::c] :opt [(and ::d ::e ::f)])  

and that fails an assertion.  It appears that the logicals (and, or) are not allowed in the optional section?

Am I thinking about this in the wrong way?  

--Scott

Rich Hickey

unread,
May 24, 2016, 1:57:26 PM5/24/16
to clo...@googlegroups.com
‘and' and ‘or’ are not currently supported in :opt

Ambrose Bonnaire-Sergeant

unread,
May 24, 2016, 2:05:36 PM5/24/16
to clojure
Thanks Rich. Now it seems test.check is eager to generate very large individual samples, which sounds
like a different problem. 

(s/def ::a (s/nilable (s/cat :a ::a
                             :b ::b
                             :c ::c)))
(s/def ::b (s/nilable (s/cat :a ::a
                             :b ::b
                             :c ::c)))
(s/def ::c (s/nilable (s/cat :a ::a
                             :b ::b
                             :c ::c)))
(time
  (count
    (binding [s/*recursion-limit* 2]
      (gen/sample (s/gen ::a) 3))))
;"Elapsed time: 50106.721779 msecs"
3

Thanks,
Ambrose

scott stackelhouse

unread,
May 24, 2016, 2:08:27 PM5/24/16
to Clojure
Ok.  

Thanks all who have worked on this, btw.  It is incredibly timely for me and is already great help for a work project.

--Scott

scott stackelhouse

unread,
May 24, 2016, 2:12:59 PM5/24/16
to Clojure
I restructured my data to make this section an optional sub-map, which I think is actually better anyway.

Rich Hickey

unread,
May 24, 2016, 3:01:32 PM5/24/16
to clo...@googlegroups.com
Fixed on master, thanks for the report.

Stan Dyck

unread,
May 24, 2016, 7:38:03 PM5/24/16
to clo...@googlegroups.com
I appreciate the wisdom of separating the existence of a key from the
conformance of the values of that key.

I wonder though if there is a way to specify co-constraints; a situation
where the conformance of the value of one key depends on the existence
of and perhaps the value of another key.

Say for example, I want to specify a ::content-type key and a ::body
key. If the ::content-type key exists and has a value of "image/png" I
want to make sure the ::body key also exists and satisfies my image?
predicate.

It looks like multi-specs get me to the neighborhood of a solution here
but I'm not sure they quite cover it.


Thanks,

StanD.


Message has been deleted

Elliot

unread,
May 24, 2016, 7:45:04 PM5/24/16
to Clojure
Super super excited for this feature, thanks so much for creating this.

In the runtime-validation case, the guide mentions:

1. Calling `valid?` in a precondition
2. Calling `conform` in the fn implementation

However neither of these appear to use the `fdef`/`instrument` combo, which seems the closest to "type annotating" the function.  Would you ever expect to use fdef/instrument active in production for validation, or is that a misunderstanding of its use?

Thanks!

- E

Kevin Corcoran

unread,
May 25, 2016, 7:45:05 AM5/25/16
to clo...@googlegroups.com
Would it be a reasonable feature request to ask for Java classes to be implicitly converted to specs, the same as predicates?  FWIW, plumatic/schema allows classes to be used as schemas.

The spec guide contains this example:

(import java.util.Date) (s/valid? #(instance? Date %) (Date.)) ;; true

... and then, later, defines:

(s/def ::date #(instance? Date %))

If classes were implicitly converted to specs, ::date would be unnecessary, and the first example could be simplified to:

(import java.util.Date) (s/valid? Date (Date.)) ;; true

I do a lot of inter-op and this seems like it would be really convenient.

Christophe Grand

unread,
May 25, 2016, 7:56:25 AM5/25/16
to clojure
Speaking of recursive definitions: how to provide an override in a recursive spec?
Would it be possible to have path suffixes (in addition to paths not in replacement of) in the override map?

Thanks,

Christophe

On Mon, May 23, 2016 at 6:16 PM, Alex Miller <al...@puredanger.com> wrote:
Yes, you can create recursive definitions by registering a spec that refers to itself via registered name (a namespaced keyword).


On Monday, May 23, 2016 at 11:13:24 AM UTC-5, Andrey Grin wrote:
 Is it planned to support recursive definitions? Example from.plumatic schema:
 
(def BinaryTree 
  (maybe ;; any empty binary tree is represented by nil
   {:value long 
    :left (recursive #'BinaryTree) 
    :right (recursive #'BinaryTree)}))

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



--
On Clojure http://clj-me.cgrand.net/
Clojure Programming http://clojurebook.com
Training, Consulting & Contracting http://lambdanext.eu/

Rich Hickey

unread,
May 25, 2016, 8:24:05 AM5/25/16
to clo...@googlegroups.com
Once you are talking about some cross-key predicate you wish to satisfy you are talking about another property of the map itself. So, you can use composition to provide further constraints on the map:

(s/and (s/keys …) cross-cutting-pred)

keys is not going to be the place for that kind of cross-cutting logic, although it does have support for some keyset-membership logic (and/or in the :req set).

multi-specs are more for when you have an open set of possibilities. s/or is to multi-spec as c.c.cond is to multimethods, closed vs open dispatch.

Rich Hickey

unread,
May 25, 2016, 8:30:03 AM5/25/16
to clo...@googlegroups.com
> Would you ever expect to use fdef/instrument active in production for validation

No, definitely not. It’s that kind of runtime checking (and expense) that gives some dynamic lang checking systems a bad rep.

The philosophy is - generative testing has made sure your function complies with the specs. So, testing the :ret and :fn properties over and over is redundant and serves no point.

OTOH, you may encounter user- or externally-supplied data at runtime and want to use the facilities of spec to validate/process it. Then you can use valid? or conform *explicitly* to do so.

The intent is that running with wrappers (instrumentation) should be done only during testing.

Gary Trakhman

unread,
May 25, 2016, 8:37:07 AM5/25/16
to clo...@googlegroups.com
Is there a public way to get the registry's spec at a keyword?  I can envision other libs being built around the spec registry, and I'm trying to write a small one (coercions) to see if I can use spec for this use-case.  Is that non-sanctioned kind of thing going forward?

The likely candidate seems 'specize'.

Gary Trakhman

unread,
May 25, 2016, 8:55:16 AM5/25/16