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
Related
I want to write a clojure spec for a hash-map wherein the value of one of the
keys is constrained to be equal to the sum of the values of two other keys. I
know one way to write a test generator for such a spec by hand:
(ns my-domain)
(require '[clojure.test :refer :all ]
'[clojure.spec.alpha :as s ]
'[clojure.spec.gen.alpha :as gen ]
'[clojure.pprint :refer (pprint) ])
(s/def ::station-id string?)
(s/def ::sim-time (s/double-in :infinite? true, :NaN? false))
(s/def ::reserved-counts (s/and int? #(not (neg? %))))
(s/def ::free-counts (s/and int? #(not (neg? %))))
(def counts-preimage (s/gen (s/keys :req [::station-id
::sim-time
::reserved-counts
::free-counts])))
(pprint (gen/generate
(gen/bind
counts-preimage
#(gen/return
(into % {::total-counts
(+ (::reserved-counts %)
(::free-counts %))})))))
#:my-domain{:station-id "sHN8Ce0tKWSdXmRd4e46fB",
:sim-time -3.4619293212890625,
:reserved-counts 58,
:free-counts 194,
:total-counts 252}
But I haven't figured out how to write a spec for it, let alone a spec that
produces a similar generator. The gist of the problem is that I lack, in the space of specs, a way to
get hold of the "preimage" in the spec, that is, I lack an analogue to bind
from the space of generators. Here is a failed attempt:
(s/def ::counts-partial-hash-map
(s/keys :req [::station-id
::sim-time
::reserved-counts
::free-counts]))
(s/def ::counts-attempted-hash-map
(s/and ::counts-partial-hash-map
#(into % {::total-counts (+ (::reserved-counts %)
(::free-counts %))})))
(pprint (gen/generate (s/gen ::counts-attempted-hash-map)))
#:my-domain{:station-id "ls5qBUoF",
:sim-time ##Inf,
:reserved-counts 56797960,
:free-counts 17}
The generated sample conforms to the spec because #(into % {...}) is truthy,
but the result doesn't contain the new attribute with the key ::total-counts.
I'd be grateful for any guidance.
EDIT: Today I Learned about s/with-gen, which will allow me to attach
my (working) test generator to my "preimage" or "partial" spec. Perhaps
that's the best way forward?
You could use the nat-int? predicate (for which there's a built-in spec, thanks #glts) for the count keys, and add a ::total-counts spec too:
(s/def ::reserved-counts nat-int?)
(s/def ::free-counts nat-int?)
(s/def ::total-counts nat-int?)
(s/def ::counts-partial-hash-map
(s/keys :req [::station-id
::sim-time
::reserved-counts
::free-counts]))
spec for a hash-map wherein the value of one of the keys is constrained to be equal to the sum of the values of two other keys
To add this assertion you can s/and a predicate function with the keys spec (or in this example the merge spec that merges the partial map spec with a ::total-count keys spec):
(s/def ::counts-attempted-hash-map
(s/with-gen
;; keys spec + sum-check predicate
(s/and
(s/merge ::counts-partial-hash-map (s/keys :req [::total-counts]))
#(= (::total-counts %) (+ (::reserved-counts %) (::free-counts %))))
;; custom generator
#(gen/fmap
(fn [m]
(assoc m ::total-counts (+ (::reserved-counts m) (::free-counts m))))
(s/gen ::counts-partial-hash-map))))
This also uses with-gen to associate a custom generator with the spec that sets ::total-count to the sum of the other count keys.
(gen/sample (s/gen ::counts-attempted-hash-map) 1)
=> (#:user{:station-id "", :sim-time 0.5, :reserved-counts 1, :free-counts 1, :total-counts 2})
The generated sample conforms to the spec because #(into % {...}) is truthy, but the result doesn't contain the new attribute with the key ::total-counts.
I'd recommend against using specs to calculate/add ::total-counts to the map. Specs generally shouldn't be used for data transformation.
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)
Let's say I have a function that takes a function and returns a function that applies any arguments it is given to the passed in function and puts the result in a vector (it's a noddy example, but will hopefully illustrate my point).
(defn box [f]
(fn [& args]
[(apply f args)]))
I think the spec for the box function looks like this
(spec/fdef box
:args (spec/cat :function (spec/fspec :args (spec/* any?)
:ret any?))
:ret (spec/fspec :args (spec/* any?)
:ret (spec/coll-of any? :kind vector? :count 1)))
If I then instrument the box function
(spec-test/instrument)
and call box with clojure.core/+ I get an exception
(box +)
ExceptionInfo Call to #'user/box did not conform to spec:
In: [0] val: ([]) fails at: [:args :function] predicate: (apply fn), Cannot cast clojure.lang.PersistentVector to java.lang.Number
:clojure.spec.alpha/args (#function[clojure.core/+])
:clojure.spec.alpha/failure :instrument
:clojure.spec.test.alpha/caller {:file "form-init4108179545917399145.clj", :line 1, :var-scope user/eval28136}
clojure.core/ex-info (core.clj:4725)
If I understand the error correctly then it's taking the any? predicate and generating a PersistentVector for the test, which clojure.core/+ obviously can't use. This means I can get it to work by changing box's argument function spec to be
(spec/fspec :args (spec/* number?)
:ret number?)
but what if I want to use box for both clojure.core/+ and clojure.string/lower-case?
N.B. To get spec to work in the REPL I need
:dependencies [[org.clojure/clojure "1.9.0-alpha16"]]
:profiles {:dev {:dependencies [[org.clojure/test.check "0.9.0"]]}}
:monkeypatch-clojure-test false
in project.clj and the following imports
(require '[clojure.spec.test.alpha :as spec-test])
(require '[clojure.spec.alpha :as spec])
I don't think you can express this function's type with clojure.spec. You would need type variables to be able to write something like (here using a Haskell-style signature)
box :: (a -> b) -> (a -> [b])
That is, it's important that you be able to "capture" the spec of the input function f and include parts of it in your output spec. But there is no such thing in clojure.spec as far as I know. You can also see that clojure.spec's list of specs for built-in functions does not define a spec for, for example, clojure.core/map, which would have the same problem.
Like #amalloy's answer says, the type (spec) of your Higher Order Function's return value depends on the argument you gave it. If you provide a function that can operate on numbers, then the function the HOF returns can also operate on numbers; if it works on strings, then strings, and so on. So, you would need to somehow inherit/reflect on the (spec of the) argument function to provide a correct output spec for the HOF, which I cannot think how.
In any case, I would opt to create separate functions (aliases) for different use cases:
(def any-box box)
(def number-box box)
Then, you can spec these independently:
(spec/fdef any-box ;... like your original spec for box
(spec/fdef number-box
:args (spec/cat :function (spec/fspec :args (spec/* number?)
:ret number?))
:ret (spec/fspec :args (spec/* number?)
:ret (spec/coll-of number? :kind vector? :count 1)))
The specs work with instrument as expected:
(spec-test/instrument)
(number-box +)
(any-box list)
Of course, writing a spec for every use case may be quite an effort, if you have many of them.
This question might be very basic, but I am new to clojure and could not figure out how to proceed with this.
abc.clj :
(ns abc)
(defn foo
[i]
(+ i 20))
I am writing clojure spec for this function in another file abc_test.clj.
(ns abc_test
(:require [clojure.spec :as s]
[clojure.spec.test :as stest]
[clojure.test :refer [deftest is run-tests]]
[abc :refer [foo]]
))
(s/fdef foo
:args (s/cat :i string?)
:ret string?
:fn #(> (:ret %) (-> % :args :i)))
(deftest test_foo
(is (empty? (stest/check `foo))))
(run-tests)
This test works absolutely fine (test should fail) if I put the function (foo) in abc_test namespace but if I require it (like above), then the test gives incorrect result.
Not sure what is going wrong here. Any heads up shall be helpful.
Thanks.
In the s/fdef, the symbol name needs to resolve to a fully-qualified symbol. The way you have it, foo is resolving to abc_test/foo. You want it to refer to foo in the other namespace:
(s/fdef abc/foo
:args (s/cat :i string?)
:ret string?
:fn #(> (:ret %) (-> % :args :i)))
Or another trick is to leverage syntax quote (which will resolve symbols inside it given the current namespace mappings):
(s/fdef `foo
:args (s/cat :i string?)
:ret string?
:fn #(> (:ret %) (-> % :args :i)))
One of the examples in the clojure.spec Guide is a simple option-parsing spec:
(require '[clojure.spec :as s])
(s/def ::config
(s/* (s/cat :prop string?
:val (s/alt :s string? :b boolean?))))
(s/conform ::config ["-server" "foo" "-verbose" true "-user" "joe"])
;;=> [{:prop "-server", :val [:s "foo"]}
;; {:prop "-verbose", :val [:b true]}
;; {:prop "-user", :val [:s "joe"]}]
Later, in the validation section, a function is defined that internally conforms its input using this spec:
(defn- set-config [prop val]
(println "set" prop val))
(defn configure [input]
(let [parsed (s/conform ::config input)]
(if (= parsed ::s/invalid)
(throw (ex-info "Invalid input" (s/explain-data ::config input)))
(doseq [{prop :prop [_ val] :val} parsed]
(set-config (subs prop 1) val)))))
(configure ["-server" "foo" "-verbose" true "-user" "joe"])
;; set server foo
;; set verbose true
;; set user joe
;;=> nil
Since the guide is meant to be easy to follow from the REPL, all of this code is evaluated in the same namespace. In this answer, though, #levand recommends putting specs in separate namespaces:
I usually put specs in their own namespace, alongside the namespace that they are describing.
This would break the usage of ::config above, but that problem can be remedied:
It is preferable for spec key names to be in the namespace of the code, however, not the namespace of the spec. This is still easy to do by using a namespace alias on the keyword:
(ns my.app.foo.specs
(:require [my.app.foo :as f]))
(s/def ::f/name string?)
He goes on to explain that specs and implementations could be put in the same namespace, but it wouldn't be ideal:
While I certainly could put them right alongside the spec'd code in the same file, that hurts readability IMO.
However, I'm having trouble seeing how this can work with destructuring. As an example, I put together a little Boot project with the above code translated into multiple namespaces.
boot.properties:
BOOT_CLOJURE_VERSION=1.9.0-alpha7
src/example/core.clj:
(ns example.core
(:require [clojure.spec :as s]))
(defn- set-config [prop val]
(println "set" prop val))
(defn configure [input]
(let [parsed (s/conform ::config input)]
(if (= parsed ::s/invalid)
(throw (ex-info "Invalid input" (s/explain-data ::config input)))
(doseq [{prop :prop [_ val] :val} parsed]
(set-config (subs prop 1) val)))))
src/example/spec.clj:
(ns example.spec
(:require [clojure.spec :as s]
[example.core :as core]))
(s/def ::core/config
(s/* (s/cat :prop string?
:val (s/alt :s string? :b boolean?))))
build.boot:
(set-env! :source-paths #{"src"})
(require '[example.core :as core])
(deftask run []
(with-pass-thru _
(core/configure ["-server" "foo" "-verbose" true "-user" "joe"])))
But of course, when I actually run this, I get an error:
$ boot run
clojure.lang.ExceptionInfo: Unable to resolve spec: :example.core/config
I could fix this problem by adding (require 'example.spec) to build.boot, but that's ugly and error-prone, and will only become more so as my number of spec namespaces increases. I can't require the spec namespace from the implementation namespace, for several reasons. Here's an example that uses fdef.
boot.properties:
BOOT_CLOJURE_VERSION=1.9.0-alpha7
src/example/spec.clj:
(ns example.spec
(:require [clojure.spec :as s]))
(alias 'core 'example.core)
(s/fdef core/divisible?
:args (s/cat :x integer? :y (s/and integer? (complement zero?)))
:ret boolean?)
(s/fdef core/prime?
:args (s/cat :x integer?)
:ret boolean?)
(s/fdef core/factor
:args (s/cat :x (s/and integer? pos?))
:ret (s/map-of (s/and integer? core/prime?) (s/and integer? pos?))
:fn #(== (-> % :args :x) (apply * (for [[a b] (:ret %)] (Math/pow a b)))))
src/example/core.clj:
(ns example.core
(:require [example.spec]))
(defn divisible? [x y]
(zero? (rem x y)))
(defn prime? [x]
(and (< 1 x)
(not-any? (partial divisible? x)
(range 2 (inc (Math/floor (Math/sqrt x)))))))
(defn factor [x]
(loop [x x y 2 factors {}]
(let [add #(update factors % (fnil inc 0))]
(cond
(< x 2) factors
(< x (* y y)) (add x)
(divisible? x y) (recur (/ x y) y (add y))
:else (recur x (inc y) factors)))))
build.boot:
(set-env!
:source-paths #{"src"}
:dependencies '[[org.clojure/test.check "0.9.0" :scope "test"]])
(require '[clojure.spec.test :as stest]
'[example.core :as core])
(deftask run []
(with-pass-thru _
(prn (stest/run-all-tests))))
The first problem is the most obvious:
$ boot run
clojure.lang.ExceptionInfo: No such var: core/prime?
data: {:file "example/spec.clj", :line 16}
java.lang.RuntimeException: No such var: core/prime?
In my spec for factor, I want to use my prime? predicate to validate the returned factors. The cool thing about this factor spec is that, assuming prime? is correct, it both completely documents the factor function and eliminates the need for me to write any other tests for that function. But if you think that's just too cool, you can replace it with pos? or something.
Unsurprisingly, though, you'll still get an error when you try boot run again, this time complaining that the :args spec for either #'example.core/divisible? or #'example.core/prime? or #'example.core/factor (whichever it happens to try first) is missing. This is because, regardless of whether you alias a namespace or not, fdef won't use that alias unless the symbol you give it names a var that already exists. If the var doesn't exist, the symbol doesn't get expanded. (For even more fun, remove the :as core from build.boot and see what happens.)
If you want to keep that alias, you need to remove the (:require [example.spec]) from example.core and add a (require 'example.spec) to build.boot. Of course, that require needs to come after the one for example.core, or it won't work. And at that point, why not just put the require directly into example.spec?
All of these problems would be solved by putting the specs in the same file as the implementations. So, should I really put specs in separate namespaces from implementations? If so, how can the problems I've detailed above be solved?
This question demonstrates an important distinction between specs used within an application and specs used to test the application.
Specs used within the app to conform or validate input — like :example.core/config here — are part of the application code. They may be in the same file where they are used or in a separate file. In the latter case, the application code must :require the specs, just like any other code.
Specs used as tests are loaded after the code they specify. These are your fdefs and generators. You can put these in a separate namespace from the code — even in a separate directory, not packaged with your application — and they will :require the code.
It's possible you have some predicates or utility functions that are used by both kinds of specs. These would go in a separate namespace all of their own.