I want to write some spec's for a small side project of mine and being worried how to write a spec for a function which does not provide any arguments to pass into it.
I want to write the spec for this particular function:
(defn get-total-pages []
(int (Math/ceil (/ (count (get-posts)) posts-per-page))))
It calculates the total number of pages assuming posts-per-page posts at one page. The function itself works well, but when adding the following spec to it:
(s/fdef get-total-pages
:args ()
:ret int?)
I can not perform any stest/check on it without the warning:
:clojure.spec.test.check/ret {:result #error {
:cause "Unable to construct gen at: [] for: clojure.spec.alpha$spec_impl$reify__2059#1232bda3"
...
When not using the :args mapping it all, spec tell me that :args is missing, so here my final question: How can I spec a function which do not offer arguments?
The :args list for a function of no arguments can be spec'd as an empty s/cat spec:
(s/fdef get-total-pages
:args (s/cat)
:ret int?)
Related
In Clojure Spec, is there a way to assert that something must be a predicate?
By predicate I mean a function which returns a true/false value.
I'd like to store some predicates in a collection. But is there a way to Spec this collection?
This is possible using fspec, with some caveats:
You must specify an :args spec; not just a :ret spec. This could be an issue if the predicates have a variety of fixed arities, or might throw on invalid inputs e.g. (pos? "1").
Each predicate in the collection will be invoked several times each to ensure they conform to the spec.
If your predicates all take a single input, you could do something like this:
(s/def ::pred (s/fspec :args (s/tuple any?) :ret boolean?))
(s/def ::pred-coll (s/coll-of ::pred))
(s/valid? ::pred-coll [boolean? number?]) => true
To illustrate that the predicates will be invoked:
(defn one? [x]
(prn x)
(= 1 x))
(s/valid? ::pred-coll [one?])
nil
()
:tL
(#uuid "9023252f-d4fe-4ee5-b526-13835cd52187")
...
=> true
Say I have a function a that takes a function (fn) as an argument:
(defn a [f] ....).
For the sake of giving a nice error message to the caller, I would at runtime validate the fn argument.
Is this possible, and how would I go about this? Can I just at runtime fdef this, and then instrument it upon calling, something like:
(defn a [f]
;;.... spec f here on the fly, and call f, throw exception if f
;; does not conform to spec
)
Is that wise or does it make sense from a philosophical stand point?
Can I just at runtime fdef this, and then instrument it upon calling [...]
No, because instrument takes a symbol, resolves its var, and essentially replaces it with a new function that wraps the original — that's where the work of validating the input arguments is done. In your example you won't have access to the f function's "original" symbol, and anonymous functions don't resolve to a var.
You can spec higher-order functions, so you could spec a like this:
(defn a [f] (f (rand-int)))
(s/fdef a :args (s/cat :f (s/fspec :args (s/cat :x number?))))
(st/instrument `a)
(a inc) ;; => 2
(a str) ;; => "1"
(a (fn [x] (assoc x :foo 'bar))) ;; spec error b/c fn doesn't conform
But... beware that any function passed as f to instrumented a will be invoked several times with random inputs! This is how spec determines if the function conforms.
(a #(doto % prn inc))
-1
-0.75
...
=> 0.18977464236944408
Obviously you wouldn't want to use this with side-effecting or expensive functions. I would only recommend using instrument for testing/development. You could also use s/assert in your function, but this will still invoke f multiple times.
(s/def ::f (s/fspec :args (s/cat :x number?)))
(defn a [f]
(s/assert ::f f)
(f (rand)))
(s/check-asserts true)
Is that wise or does it make sense from a philosophical stand point?
It really depends on the nature of your program and the possible domain of functions passed as f.
Not sure how to write a Spec to check the destructured arguments to a function.
If I have this simple function:
(defn make-name [[first last]]
(str first " " last))
And I write this Spec for the argument:
(s/def ::vstring2 (s/coll-of string? :kind vector? :count 2 :distinct false))
It works correctly:
(s/conform ::vstring2 ["Rich" "Hickey"])
=> ["Rich" "Hickey"]
But when I try to apply it to the function like so:
(s/fdef make-name
:args ::vstring2
:ret string?)
It blows up:
(stest/check `make-name)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
0x7dd4c5ac
"clojure.spec.alpha$fspec_impl$reify__2451#7dd4c5ac"],
:clojure.spec.test.check/ret {:result #error{:cause "Wrong number of args (2) passed to: roster/make-name",
(...)
How do I write the function Spec using ::vstring2? Or do I?
Thank you.
You just need to specify another spec for your function's arguments e.g. using s/cat:
(s/fdef make-name
:args (s/cat :arg1 ::vstring2)
:ret string?)
In your example, the :args spec is expecting two arguments because your ::vstring2 spec is a collection expecting two strings. With this change, the function :args spec knows that it only takes one argument which should conform to ::vstring2.
There are more function spec examples here and here.
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.
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