Let's consider a Clojure Spec regexp for hiccup syntax
(require '[clojure.spec :as spec])
(spec/def ::hiccup
(spec/cat :tag keyword?
:attributes (spec/? map?)
:content (spec/* (spec/or :terminal string?
:element ::hiccup))))
which works splendidly
(spec/conform ::hiccup [:div#app [:h5 {:id "loading-message"} "Connecting..."]])
; => {:tag :div#app, :content [[:element {:tag :h5, :attributes {:id "loading-message"}, :content [[:terminal "Connecting..."]]}]]}
until you try to generate some example data for your functions from the spec
(require '[clojure.spec.gen :as gen])
(gen/generate (spec/gen ::hiccup))
; No return value but:
; 1. Unhandled java.lang.OutOfMemoryError
; GC overhead limit exceeded
Is there a way to rewrite the spec so that it produces a working generator? Or do we have to attach some simplified generator to the spec?
The intent of spec/*recursion-limit* (default 4) is to limit recursive generation such that this should work. So either that's not working properly in one of the spec impls (* or or), or you are seeing rapid growth in something else (like map? or the strings). Without doing some tinkering, it's hard to know which is the problem.
This does generate (a very large example) for me:
(binding [spec/*recursion-limit* 1] (gen/generate (spec/gen ::hiccup)))
I do see several areas where the cardinalities are large even in that one example - the * and the size of the generated attributes map?. Both of those could be further constrained. It would be easiest to break these parts up further into more fine-grained specs and supply override generators where necessary (the attribute map could just be handled with map-of and :gen-max).
Related
Could someone shed a light on the following behavior please?
Let's assume I have this namespace with a spec:
(ns user.specs
(:require [clojure.alpha.spec :as s]
[clojure.alpha.spec.gen :as gen]
[clojure.string :as str]))
# Non-blank string of 20 to 50 ascii chars.
(s/def ::text (s/with-gen
(s/and string? #(not (str/blank? %)))
#(gen/such-that
(complement str/blank?)
(gen/fmap
clojure.string/join
(gen/vector
(gen/char)
20 50)))))
Now I want to reuse this spec.
(in-ns 'user)
(require '[user.specs :as su])
=> nil
(def kws [::dir
::ns])
=> #'user/kws
(s/def ::dir ::su/text)
=> :user/dir
(s/def ::ns string?)
=> :user/ns
(s/register ::spec (s/schema* kws))
=> :user/spec
When exercising the last spec, I get an error:
(s/exercise ::spec)
Error printing return value (IllegalArgumentException) at clojure.core/-cache-protocol-fn (core_deftype.clj:583).
No implementation of method: :conform* of protocol: #'clojure.alpha.spec.protocols/Spec found for class: clojure.lang.Keyword
However, if I redef the ::dir spec usinig s/register and s/get-spec instead of s/def, no problem:
(s/register ::dir (s/get-spec ::su/text))
=> :user/dir
(s/exercise ::spec)
=>
([#:user{:dir "teôÆ>EüáéNj¬u}zþs²DÍ$", :ns ""}
#:user{:dir "teôÆ>EüáéNj¬u}zþs²DÍ$", :ns ""}]
[#:user{:dir ":éû,#Î|)Q«óCS\t´ÿ4ÚÝܺ»Ân5Zq", :ns ""}
#:user{:dir ":éû,#Î|)Q«óCS\t´ÿ4ÚÝܺ»Ân5Zq", :ns ""}]
... elided
I'm assuming, from the error message, that with s/def, spec resolves ::dir as the literal ::su/text keyword instead of the associated spec.
1) Why?
2) Is s/register + s/get-spec an appropriate solution?
I'm trying to reuse a "utility" spec in a few places under domain specific names.
FWIW, I'm using spec-alpha2 in order to build specs dynamically and benefit from schema + select.
Aliasing specs like (s/def ::dir ::su/text) is not currently working in spec 2, which is still a work in progress.
I've recently been trying out Clojure Spec and ran into an unexpected error message. I've figured out that if you have a spec/or nested in a spec/and then the spec functions, after the spec/or branch, get passed a conformed value rather than the top level value.
You can see this in the printed value of "v" here (contrived example):
(spec/valid? (spec/and (spec/or :always-true (constantly true))
(fn [v]
(println "v:" v)
(constantly true)))
nil)
v: [:always-true nil]
=> true
I think this may be intentional from the doc string of spec/and:
Takes predicate/spec-forms, e.g.
(s/and even? #(< % 42))
Returns a spec that returns the conformed value. Successive
conformed values propagate through rest of predicates.
But this seems counterintuitive to me as it would hamper reuse of spec predicates, because they'd need to be written to accept "[<or branch> <actual value>]".
Things get even worse if you have multiple spec/or branches:
(spec/valid? (spec/and (spec/or :always-true (constantly true))
(spec/or :also-always-true (constantly true))
(fn [v]
(println "v:" v)
(constantly true)))
nil)
v: [:also-always-true [:always-true nil]]
=> true
Have I missed something fundamental here?
But this seems counterintuitive to me as it would hamper reuse of spec predicates
IMO the alternatives to these behaviors are less appealing:
Discard s/or's conformed tags by default. We can always discard it if we want, but we wouldn't want clojure.spec to make that decision for us. Spec assumes we want to know which s/or branch matched.
Don't flow conformed values in s/and, at the expense of spec/predicate composability.
Luckily we can discard the s/or tags if necessary. Here are two options:
Wrap the s/or in s/noncomforming. Thanks to glts' comment below reminding me about this (undocumented) function!
(s/valid?
(s/and
(s/nonconforming (s/or :s string? :v vector?))
empty?)
"")
=> true
s/and the s/or specs with a s/conformer that discards the tag.
(s/valid?
(s/and
(s/and (s/or :s string? :v vector?)
;; discard `s/or` tag here
(s/conformer second))
empty?)
[])
=> true
If you often needed this, you could reduce boilerplate with a macro:
(defmacro dkdc-or [& key-pred-forms]
`(s/and (s/or ~#key-pred-forms) (s/conformer second)))
Things get even worse if you have multiple spec/or branches
If you're writing a spec for data that allows for alternatives (e.g. s/or, s/alt), and you're "flowing" valid alternatives into subsequent predicates (s/and), IMO it's more generally useful to have that knowledge in subsequent predicates. I'd be interested in seeing a more realistic use case for this type of spec, because there might be a better way to spec it.
Complementary comment: the behavior of Spec spotted by the question means that you can very well have (and (s/valid? ::spec-1 v) (s/valid? ::spec2) v) but not (s/valid? (s/and ::spec-1 ::spec-2) v), due to conforming from ::spec-1.
You might understandably find this behavior of s/and surprising (not to mention insane). Note however that you will have (s/valid? (s/and (s/nonconforming ::spec-1) ::spec-2) v), as mentioned by the accepted answer.
The corollary is that when using s/and, ask yourself whether you need s/conforming. This piece of advice might be a worthwhile addition to the docstring of s/and.
I am super confused by Clojure Spec. When I run in the repl by entering:
(require '[clojure.spec.alpha :as s])
And then add:
(s/valid? even? 10)
I get //true. And when I run:
(s/valid? even? 11)
//False. Ok so that works. Then when I require spec in my core.clj as:
(ns spam-problem.core
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]))
And try a simple validation to get it to throw an error, nothing happens:
(defn -main
"I don't do a whole lot ... yet."
[& args]
(s/valid? even? 11))
I have no idea what I'm doing wrong here and am very confused about how spec is supposed to work. I am running this by using command lein run. Is there another way you're supposed to run it?
I understand what you are feeling because once I got into Spec it caused me the same thoughts. What really helped me to solve the problem in my mind is to considering Spec being not a final library but rather a framework. In my projects, usually I've got a special module with high-level wrappers above basic spec capabilities. I believe, you might do the same: define a function that takes data, spec and raises those error message you desire to have in terms of your business-logic. Here is a small example of my code:
(ns project.spec
(:require [clojure.spec.alpha :as s]))
;; it's better to define that value is a constant
(def invalid :clojure.spec.alpha/invalid)
(defn validate
"Either returns coerced data or nil in case of error."
[spec value]
(let [result (s/conform spec value)]
(if (= result invalid)
nil
result)))
(defn spec-error
"Returns an error map for data structure that does not fit spec."
[spec data]
(s/explain-data spec data))
Now, let's prepare some specs:
(defn x-integer? [x]
(if (integer? x)
x
(if (string? x)
(try
(Integer/parseInt x)
(catch Exception e
invalid))
invalid)))
(def ->int (s/conformer x-integer?))
(s/def :opt.visits/fromDate ->int)
(s/def :opt.visits/toDate ->int)
(s/def :opt.visits/country string?)
(s/def :opt.visits/toDistance ->int)
(s/def :opt.visits/params
(s/keys :opt-un [:opt.visits/fromDate
:opt.visits/toDate
:opt.visits/country
:opt.visits/toDistance]))
And here are some usage examples:
(let [spec :opt.visits/params
data {:some :map :goes :here}]
(if-let [cleaned-data (validate spec data)]
;; cleaned-data has values coerced from strings to integers,
;; quite useful for POST parameters
(positive-logic cleaned-data)
;; error values holds a map that describes an error
(let [error (spec-error spec data)]
(error-logic-goes-here error))))
What might be improved here is to have a combo-function with both validate and error functionality. Such a function could return a vector of two values: success flag and either result or error data structure as follows:
[true {:foo 42}] ;; good result
[false {:error :map}] ;; bad result
The Spec library does not dictate a single way of processing data; that's why it's really good and flexible.
valid? is a predicate that returns true or false. Your program isn’t doing anything with the return value. Try printing it to console or using s/assert if you want to throw an exception:
If (check-asserts?) is false at runtime, always returns x. Defaults to
value of 'clojure.spec.check-asserts' system property, or false if not
set. You can toggle check-asserts? with (check-asserts bool).
So you may need to set (s/check-asserts true) to have s/assert throw exceptions:
(clojure.spec.alpha/assert even? 3)
=> 3
(clojure.spec.alpha/check-asserts?)
=> false
(clojure.spec.alpha/check-asserts true)
=> true
(clojure.spec.alpha/assert even? 3)
ExceptionInfo Spec assertion failed
val: 3 fails predicate: :clojure.spec.alpha/unknown
clojure.core/ex-info (core.clj:4739)
I have a DSL specification which is a sequence as usual (cat). I want to take advantage of spec's parsing (i.e. conforming) to get the AST of an expression that conforms with my DSL. E.g.
user> (s/def ::person (s/cat :person-sym '#{person} :name string? :age number?))
=> :user/person
user> (s/conform ::person '(person "Henry The Sloth" 55))
=> {:person-sym person, :name "Henry The Sloth", :age 55}
Now that it's parsed and I have my AST, I would want to do interesting things with it, so I would want to test it and whatnot. So now I need to write a spec for that AST, and that's basically duplicating everything. Actually it's worse than that because now I have to s/def specs for predicates that I didn't have to before, because as the docs for keys says: "there is no support for inline value specification, by design." / "It is the (enforced) opinion of spec that the specification of values associated with a namespaced keyword, like :my.ns/k, should be registered under that keyword itself..". So duplicating (with omitting the person-sym part):
user> (s/def ::name string?)
=> :user/name
user> (s/def ::age number?)
=> :user/age
user> (s/def ::person-ast (s/keys :req-un [::name ::age]))
:user/person-ast
And now it seems to be compatible:
user> (s/conform ::person-ast (s/conform ::person '(person "Henry The Sloth" 55)))
=> {:person-sym person, :name "Henry The Sloth", :age 55}
In practice, I have more complicated data of course, and I wonder what should I do? AFAIK spec doesn't give me the spec for the AST that it creates (actually personally I would figure that this is something it should do). Any suggestions?
I'd say right now you have two options - one is to do what you're doing and create two sets of specs for the before/after.
The other option is to create a model of your domain in data and generate both specs (I've seen many people are doing something like this).
I have not heard Rich talk about generating the output spec of conformed results so I don't think that is likely in the current roadmap.
I just saw one of Rich's talks on clojure.spec, and really want to give it a try on my project. I'm writing a series of tools for parsing C code using the eclipse CDT library, and I would like to spec that my functions accept and emit AST objects.
I think a very basic spec could be written for a function that takes the root of an AST and emits all the tree's leaves like this:
(import '(org.eclipse.cdt.core.dom.ast IASTNode))
(require '[clojure.spec :as s])
(defn ast-node? [node] (instance? IASTNode node))
(s/def ::ast-node ast-node?)
(s/fdef leaves :args ::ast-node :ret (s/coll-of ::ast-node))
However when I try to exercise the code (s/exercise leaves) I get an error:
Unable to construct gen at: [] for:
xxx.x$leaves#xxx
#:clojure.spec{:path [], :form #function[xxx.xxx/leaves], :failure :no-gen}
How can I write a custom generator for Java objects to fully spec and exercise my code?
You can attach a custom generator to a spec using s/with-gen. You'll need to write a generator that produces all the node variants that you need. You might find it easier to write one generator per node type and then combine them, either with s/or or possibly by using something like s/multi-spec instead (which would make this open to extension).
An example of writing a generator that produces a Java object would be something like this:
(s/def ::date
(s/with-gen #(instance? java.util.Date %)
(fn [] (gen/fmap #(java.util.Date. %) (s/gen pos-int?)))))
fmap takes a function and applies that to each result from the generator you give it. If you have a Java object with a constructor that takes multiple values, you can use a source generator like (s/gen (s/tuple int? string? int?)).
For completeness, here's my code after applying Alex's answer to spec a "LiteralExpression" AST node:
(ns atom-finder.ast-spec
(:import [org.eclipse.cdt.internal.core.dom.parser.cpp CPPASTLiteralExpression])
(:require [clojure.spec :as s]
[clojure.spec.gen :as gen]))
(def gen-literal-expression-args
(gen/one-of
[
(gen/tuple (s/gen #{CPPASTLiteralExpression/lk_char_constant})
(gen/char-ascii))
(gen/tuple (s/gen #{CPPASTLiteralExpression/lk_float_constant})
(gen/double))
(gen/tuple (s/gen #{CPPASTLiteralExpression/lk_integer_constant})
(s/gen (s/int-in -2147483648 2147483647)))
(gen/tuple (s/gen #{CPPASTLiteralExpression/lk_string_literal})
(gen/string))]))
(def gen-literal-expression
(gen/fmap
(fn [[type val]]
(CPPASTLiteralExpression. type (.toCharArray (str val))))
gen-literal-expression-args))
(s/def ::literal-expression
(s/with-gen
(partial instance? CPPASTLiteralExpression)
(fn [] gen-literal-expression)))
(s/exercise :atom-finder.ast-spec/literal-expression 10