speculative is a collection of specs for the functions in clojure.core
. While its ultimate goal is to be rendered obsolete by these or similar specs being added to clojure.core
proper, speculative hopefully provides some value while we're waiting for that to happen.
The project started based on two tweets. First @mfikes tweeted
I still hold the view that Clojure’s core fns should have specs.
— Mike Fikes (@mfikes) October 19, 2018
Ex: While
(merge-with + [0 3] {0 7 1 2} {0 3 2 32})
produces a reasonable result, it is not even a map. A spec would reject 2nd arg.
What if I conclude dot products are possible via
(merge-with + [0 3] [1 2])
?
Then @borkdude tweeted a couple of days later:
Or maybe have a development version with guards and a production version without guards (I think Stu said something like this)
— (λ. borkdude) (@borkdude) October 19, 2018
With the new error-messages that are coming with Clojure 1.10, adding specs to the clojure.core
functions give much better error messages.
Without specs on clojure.core/map
the error looks like:
Clojure 1.10.0-RC1
user=> (map 'lol 'lol)
Error printing return value (IllegalArgumentException) at clojure.lang.RT.seqFrom (RT.java:551).
Don't know how to create ISeq from: clojure.lang.Symbol
user=>
With speculative, we get
user=> (map 1 'lol)
Evaluation error - invalid arguments to clojure.core/map at (NO_SOURCE_FILE:4).
1 - failed: ifn? at: [:f]
user=>
Add the relevant coordinates to your favourite build tool:
deps.edn
speculative {:mvn/version "RELEASE"}
lein
[speculative "0.0.1"]
user=> (require 'speculative.core)
nil
user=> (require '[clojure.spec.test.alpha :as stest])
nil
user=> (stest/instrument `clojure.core/map)
[clojure.core/map]
user=> (map 1 'lol)
Evaluation error - invalid arguments to clojure.core/map at (NO_SOURCE_FILE:4).
1 - failed: ifn? at: [:f]
user=>
Namespace speculative.test
provides macros and functions that are used in the
tests for speculative, but may also come in handy in other projects. You have to
bring in macrovich as an extra dependency
if you want to use this namespace.
$ clj -Sdeps '{:deps {net.cgrand/macrovich {:mvn/version "0.2.1"}}}'
Clojure 1.10.0-RC1
user=> (require '[speculative.test :refer [check
with-instrumentation
gentest
success?]])
nil
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (s/fdef foo
:args (s/cat :n number?)
:ret number?)
user/foo
user=> (defn foo [n]
"ret")
#'user/foo
user=> (check `foo [1])
Evaluation error - invalid arguments to null at clojure.spec.test.alpha/explain-check (alpha.clj:278).
"ret" - failed: number? at: [:ret]
user=> (s/fdef foo
:args (s/cat :n number?)
:ret string?)
user/foo
user=> (check `foo [1])
"ret"
user=> (with-instrumentation `foo
(foo "a"))
Evaluation error - invalid arguments to user/foo at (NO_SOURCE_FILE:15).
"a" - failed: number? at: [:n]
user=> (foo "a")
"ret"
user=> (gentest `foo nil {:num-tests 1})
generatively testing user/foo
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2524 0x72bd06ca "clojure.spec.alpha$fspec_impl$reify__2524@72bd06ca"], :clojure.spec.test.check/ret {:result true, :pass? true, :num-tests 1, :time-elapsed-ms 1, :seed 1541249961647}, :sym user/foo})
user=> (success? *1)
true
user=>
These issues were detected by usage of speculative.
clj -A:test:clj-tests
clj -A:test:cljs-tests
plk -A:test:plk-tests
In the hope that the code in this project would be useful for clojure.core
, any contributer to this repo needs to have a
Contributor Agreement for Clojure so that any code in speculative can be used in either Clojure or Clojurescript.
Take a look at the style guide.
Copyright © 2018 Erik Assum
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.