Clojure Spec and destructuring? - clojure

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.

Related

How to spec a function which have no arguments in Clojure?

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?)

Clojure spec: Using a "spec" instead of a "pred" in "coll-of" actually works. Is this ok?

I'm trying out Clojure spec and calling it from a function :pre constraint.
My spec ::mustbe-seql-of-vec-of-2-int shall check whether the argument passed to the function is a sequential of anything from 0 to 4 vectors of exactly 2 integers:
(require '[clojure.spec.alpha :as s])
(s/def ::mustbe-seql-of-vec-of-2-int
(s/and
::justprint
(s/coll-of
::mustbe-vec-of-2-int
:kind sequential?
:min-count 0
:max-count 4)))
So the spec is composed of other specs, in particular ::justprint which does nothing except print the passed argument for debugging and coll-of to test the collection argument.
The doc for coll-of says:
Usage: (coll-of pred & opts)
Returns a spec for a collection of items satisfying pred. Unlike
'every', coll-of will exhaustively conform every value.
However, as first argument, I'm not using a pred (a function taking the argument-to-check and returning a truthy value) but another spec (a function taking the argument to check and returning I'm not sure what), in this case, the spec registered under ::mustbe-vec-of-2-int.
This works perfectly well.
Is this correct style and expected to work?
P.S.
(s/def ::justprint
#(do
; "vec" to realize the LazySeq, which is not realized by join
(print (d/join [ "::justprint ▶ " (vec %) "\n"] ))
true))
(s/def ::mustbe-vec-of-2-int
(s/coll-of integer? :kind vector? :count 2))
Is this correct style and expected to work?
Yes. Specs, keywords that can be resolved to specs, and plain predicate functions can be used interchangeably in many parts of the clojure.spec API. "Pred" in the context of that docstring has a broader meaning than a general Clojure predicate function e.g. nil?.
(require '[clojure.spec.alpha :as s])
(s/conform int? 1) ;; => 1
(s/conform int? false) ;; => :clojure.spec.alpha/invalid
s/def modifies the clojure.spec registry, associating the keyword with the spec value. When you pass ::some-spec-keyword to clojure.spec API it resolves the spec values themselves from the registry. You can also "alias" specs e.g. (s/def ::foo ::bar).
(s/def ::even-int? (s/and int? even?))
(s/conform ::even-int? 2) ;; => 2
(s/conform ::even-int? 3) ;; => :clojure.spec.alpha/invalid
So these are all equivalent:
(s/conform (s/coll-of (s/and int? even?)) [2 4 6 8])
(s/conform (s/coll-of ::even-int?) [2 4 6 8])
(s/def ::coll-of-even-ints? (s/coll-of ::even-int?))
(s/conform ::coll-of-even-ints? [2 4 6 8])

Is there a way in Clojure Spec to assert that something must be a predicate

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

How do I spec higher order function arguments in Clojure?

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.

Meaningful error message for Clojure.Spec validation in :pre

I used the last days to dig deeper into clojure.spec in Clojure and ClojureScript.
Until now I find it most useful, to use specs as guards in :pre and :post in public functions that rely on data in a certain format.
(defn person-name [person]
{:pre [(s/valid? ::person person)]
:post [(s/valid? string? %)]}
(str (::first-name person) " " (::last-name person)))
The issue with that approach is, that I get a java.lang.AssertionError: Assert failed: (s/valid? ::person person) without any information about what exactly did not met the specification.
Has anyone an idea how to get a better error message in :pre or :post guards?
I know about conform and explain*, but that does not help in those :pre or :post guards.
In newer alphas, there is now s/assert which can be used to assert that an input or return value matches a spec. If valid, the original value is returned. If invalid, an assertion error is thrown with the explain result. Assertions can be turned on or off and can even optionally be omitted from the compiled code entirely to have 0 production impact.
(s/def ::first-name string?)
(s/def ::last-name string?)
(s/def ::person (s/keys :req [::first-name ::last-name]))
(defn person-name [person]
(s/assert ::person person)
(s/assert string? (str (::first-name person) " " (::last-name person))))
(s/check-asserts true)
(person-name 10)
=> CompilerException clojure.lang.ExceptionInfo: Spec assertion failed
val: 10 fails predicate: map?
:clojure.spec/failure :assertion-failed
#:clojure.spec{:problems [{:path [], :pred map?, :val 10, :via [], :in []}], :failure :assertion-failed}
I think the idea is that you use spec/instrument to validate function input and output rather than pre and post conditions.
There's a good example toward the bottom of this blog post: http://gigasquidsoftware.com/blog/2016/05/29/one-fish-spec-fish/ . Quick summary: you can define a spec for a function, including both input and return values using the :args and :ret keys (thus replacing both pre and post conditions), with spec/fdef, instrument it, and you get output similar to using explain when it fails to meet spec.
Minimal example derived from that link:
(spec/fdef your-func
:args even?
:ret string?)
(spec/instrument #'your-func)
And that's equivalent to putting a precondition that the function has an integer argument and a postcondition that it returns a string. Except you get much more useful errors, just like you're looking for.
More details in the official guide: https://clojure.org/guides/spec ---see under the heading "Spec'ing functions".
Without taking into account if you should use pre and post conditions to validate function arguments, there is a way to print somewhat clearer messages from pre and post conditions by wrapping your predicate with clojure.test/is, as suggested in the answer below:
How can I get Clojure :pre & :post to report their failing value?
So then your code could look like this:
(ns pre-post-messages.core
(:require [clojure.spec :as s]
[clojure.test :as t]))
(defn person-name [person]
{:pre [(t/is (s/valid? ::person person))]
:post [(t/is (s/valid? string? %))]}
(str (::first-name person) " " (::last-name person)))
(def try-1
{:first-name "Anna Vissi"})
(def try-2
{::first-name "Anna"
::last-name "Vissi"
::email "Anna#Vissi.com"})
(s/def ::person (s/keys :req [::first-name ::last-name ::email]))
Evaluating
pre-post-messages.core> (person-name try-2)
would produce
"Anna Vissi"
and evaluating
pre-post-messages.core> (person-name try-1)
would produce
FAIL in () (core.clj:6)
expected: (s/valid? :pre-post-messages.core/person person)
actual: (not (s/valid? :pre-post-messages.core/person {:first-name "Anna Vissi"}))
AssertionError Assert failed: (t/is (s/valid? :pre-post-messages.core/person person)) pre-post-messages.core/person-name (core.clj:5)
This is useful when you don't want to use s/assert, or can not enable s/check-assserts. Improving on MicSokoli's answer:
:pre simply cares that the values returned are all truthy, so we can convert the return value "Success!\n" to true (for strictness) and throw an error with the explanation and the input data in case the output is not successful.
(defn validate [spec input]
(let [explanation (s/explain-str spec input)]
(if (= explanation "Success!\n")
true
(throw (ex-info explanation {:input input}))))
A variation of this could be this one, but it would run the spec twice:
(defn validate [spec input]
(if (s/valid? spec input)
true
(throw (ex-info (s/explain spec input) {:input input}))))
Usage:
(defn person-name [person]
{:pre [(validate ::person person)]}
(str (::first-name person) " " (::last-name person)))