I have a function in Clojure that accepts a 2-element vector as an argument:
(defn influence [[school value]])
I would like to write a spec for the arguments to this function using existing specs I have registered:
(s/fdef influence :args (s/cat :arg (s/cat :school ::school, :value ::value))
However, this does not work, the nested s/cat calls operate at the top level and apply the spec for ::school to the whole argument list. There is also a function called s/tuple, which might suggest you can do
(s/fdef ->influence :args (s/cat :influence (s/tuple ::school ::value)))
But this also doesn't work. Spec appears to somehow get confused and try to conform the spec name to the spec:
val: :my.ns/school fails spec: :my.ns/school at: [:args :school] predicate...
You can start off like so using a regular function of 2 args:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest]
))
(defn name-age
[name age]
(format "name=%s age=%d" name age))
(s/fdef name-age
:args (s/cat
:name string?
:age pos-int? ) )
(dotest
(spyx (name-age "joe" 42))
(stest/instrument `name-age)
(throws? (spyx (name-age "jill" :24))))
with result
(name-age ["joe" 42]) => "name=joe age=42"
and then rewrite it as a fn of 1 arg that is a tuple:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest] ))
(s/def ::name string?)
(s/def ::age pos-int?)
(s/def ::na-tup (s/cat :name-arg ::name :age-arg ::age))
(defn name-age
[na-tup]
(s/valid? ::na-tup na-tup)
(let [[name age] na-tup]
(s/valid? ::name name)
(s/valid? ::age age)
(format "name=%s age=%d" name age)))
(s/fdef name-age
:args (s/cat
:name ::name
:age ::age ) )
(dotest
(spyx (name-age ["joe" 42]))
(stest/instrument `name-age)
(throws? (spyx (name-age ["jill" :24]))) )
If desired, you can also get some help destructuring the data in the name-age function:
(s/conform ::na-tup na-tup) =>
{:name-arg "joe", :age-arg 42}
Live code examples
I worked through the Clojure Spec docs and made them into live units that may be seen here. Will probably break them out into a separate repo at some point.
Related
I am trying to use Clojure Spec to define a data structure containing a java.time.LocalDate element:
(s/def :ex/first-name string?)
(s/def :ex/last-name string?)
(s/def :ex/birth-date (s/valid? inst? (java.time.LocalDate/now)))
(s/def :ex/person
(s/keys :req [:ex/first-name
:ex/last-name
:ex/birth-date]))
(def p1 #:ex{:first-name "Jenny"
:last-name "Barnes"
:birth-date (java.time.LocalDate/parse "1910-03-15")})
(println p1)
produces the following output
#:ex{:first-name Jenny, :last-name Barnes, :birth-date #object[java.time.LocalDate 0x4ed4f9db 1910-03-15]}
However, when I test to see if p1 conforms to the :ex/person spec, it fails:
(s/valid? :ex/person p1)
ClassCastException java.lang.Boolean cannot be cast to clojure.lang.IFn clojure.spec.alpha/spec-impl/reify--1987 (alpha.clj:875)
Looking closer at the Clojure examples for inst?, I see:
(inst? (java.time.Instant/now))
;;=> true
(inst? (java.time.LocalDateTime/now))
;;=> false
However, I don't see an obvious reason as to why that returns false. This seems to be the root of my issue, but I have not found a solution and would like some help.
You're probably looking for instance?- and your example fails, because in:
(s/def :ex/birth-date (s/valid? inst? (java.time.LocalDate/now)))
this part (s/valid? inst? (java.time.LocalDate/now)) should be a function (predicate), not boolean. The full code:
(s/def :ex/first-name string?)
(s/def :ex/last-name string?)
(s/def :ex/birth-date #(instance? java.time.LocalDate %))
(s/def :ex/person
(s/keys :req [:ex/first-name
:ex/last-name
:ex/birth-date]))
(def p1 #:ex{:first-name "Jenny"
:last-name "Barnes"
:birth-date (java.time.LocalDate/parse "1910-03-15")})
(s/valid? :ex/person p1)
=> true
inst? won't work here, because Inst is a protocol, used to extend java.util.Date and java.time.Instant:
(defprotocol Inst
(inst-ms* [inst]))
(extend-protocol Inst
java.util.Date
(inst-ms* [inst] (.getTime ^java.util.Date inst)))
(defn inst?
"Return true if x satisfies Inst"
{:added "1.9"}
[x]
(satisfies? Inst x))
(extend-protocol clojure.core/Inst
java.time.Instant
(inst-ms* [inst] (.toEpochMilli ^java.time.Instant inst)))
And you can use satisfies? to check whether some object satisfies given protocol:
(satisfies? Inst (java.time.LocalDate/parse "1910-03-15"))
=> false
Can I use the Clojure spec system to define function signatures and verify if functions satisfy them?
Here are some examples I've tried without success
(s/valid? (s/fspec :args string? :ret string?) identity) ;; false
(def nope identity)
(s/valid? (s/fspec :args string? :ret string?) nope) ;; false
(s/conform (s/fspec :args string? :ret string?) identity) ;; invalid
(defn strmap [s] {:pre [(s/valid? string? s)] :post [(s/valid? string? %)]} s)
(s/valid? (s/fspec :args string? :ret string?) strmap) ;; false
(s/fdef strmap :args string? :ret string?)
(s/valid? strmap strmap) ;; true
(s/def ::str-pred (s/fspec :args string? :ret boolean?))
(s/valid ::str-pred (fn [s] true)) ;; false
I know about fdef, but I'd like something I can compose. For example, creating a map of related function signatures.
You can access the :args and :ret spec of a function using s/get-spec and then validate:
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (defn strmap [s] s)
#'user/strmap
user=> (s/fdef strmap :args string? :ret string?)
user/strmap
user=> (s/valid? (:args (s/get-spec `strmap)) "foo")
true
user=> (s/valid? (:args (s/get-spec `strmap)) :bar)
false
I am using this in re-find.web.
Turns out spec does handle functions, but it expects the arguments to be a tuple.
(s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) identity) ;; true!
(s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) (fn [x] (str x "hi there"))) ;; true
(s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) (fn [x] x)) ;; true
(s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) (fn [x] 42)) ;; false
There does appear to be some issues with the anonymous function literal. I'm probably misunderstanding something about the macro expansion.
(s/valid? (s/fspec :args (s/cat :arg1 string?) :ret string?) #(%)) ;; false
An alternative to clojure.spec, malli is a data-driven schema library that has support for function schemas
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)))
I've been getting on quite well with clojure.spec for the most part. However, I came to a problem that I couldn't figure out when dealing with unform. Here's a loose spec for Hiccup to get us moving:
(require '[clojure.spec :as s])
(s/def ::hiccup
(s/and
vector?
(s/cat
:name keyword?
:attributes (s/? map?)
:contents (s/* ::contents))))
(s/def ::contents
(s/or
:element-seq (s/* ::hiccup)
:element ::hiccup
:text string?))
Now before we get carried away, let's see if it works with a small passing case.
(def example [:div])
(->> example
(s/conform ::hiccup))
;;=> {:name :h1}
Works like a charm. But can we then undo our conformance?
(->> example
(s/conform ::hiccup)
(s/unform ::hiccup))
;;=> (:div)
Hmm, that should be a vector. Am I missing something? Let's see what spec has to say about this.
(->> example
(s/conform ::hiccup)
(s/unform ::hiccup)
(s/explain ::hiccup))
;; val: (:div) fails spec: :user/hiccup predicate: vector?
;;=> nil
Indeed, it fails. So the question: How do I get this to work correctly?
Late response here. I had not realised how old this question was but since I had already written a reply I might as well submit it.
Taking the spec that you provide for ::hiccup:
(s/def ::hiccup
(s/and
vector?
(s/cat
:name keyword?
:attributes (s/? map?)
:contents (s/* ::contents))))
The vector? spec within and will test the input data against that predicate. Unluckily as you have experienced, it doesn't unform to a vector.
Something that you can do to remedy this is to add an intermediate spec that will act as the identity when conforming and will act as desired when unforming. E.g.
(s/def ::hiccup
(s/and vector?
(s/conformer vec vec)
(s/cat
:name keyword?
:attributes (s/? map?)
:contents (s/* ::contents))))
(s/unform ::hiccup (s/conform ::hiccup [:div]))
;;=> [:div]
(s/conformer vec vec) Will be a no-op for everything that satisfies vector? and using vec as the unforming function in the conformer will make sure that the result to unforming the whole and spec stays a vector.
I am new to spec myself and this was how I got it working as you intend. Bear in mind it might not be the way in which it was intended to be used by spec's designers.
For what is worth, I made a spec that checks for a vector and unforms to a vector:
(defn vector-spec
"Create a spec that it is a vector and other conditions and unforms to a vector.
Ex (vector-spec (s/spec ::binding-form))
(vector-spec (s/* integer?))"
[form]
(let [s (s/spec (s/and vector? form))]
(reify
s/Specize
(specize* [_] s)
(specize* [_ _] s)
s/Spec
(conform* [_ x] (s/conform* s x))
(unform* [_ x] (vec (s/unform* s x))) ;; <-- important
(explain* [_ path via in x] (s/explain s path via in x))
(gen* [_ overrides path rmap] (s/gen* s overrides path rmap))
(with-gen* [_ gfn] (s/with-gen s gfn))
(describe* [_] (s/describe* s)))))
In your example, you would use it like this:
(s/def ::hiccup
(vector-spec
(s/cat
:name keyword?
:attributes (s/? map?)
:contents (s/* ::contents))))
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.