Clojure 1.9 introduces clojure.spec: tutorial with live coding examples #cljklip...
source link: https://blog.klipse.tech/clojure/2016/05/30/spec.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Clojure 1.9 introduces clojure.spec: tutorial with live coding examples #cljklipse @viebel
May 30, 2016 • Yehonathan Sharvit
What is Spec?
The spec
library specifies the structure of data, validates or destructures it, and can generate data based on the spec. The clojure.spec
(or cljs.spec
) namespace is included in the Clojure core distribution, so no extra library is required to use it. spec
is part of Clojure 1.9
.
In this article, we present spec
live coding examples provided by KLIPSE. (This article is a rewrite of http://clojure.org/guides/spec with live examples instead of static code snippets.)
All the coding examples below are live and editable:
- the code is executed in your browser while you are reading
- you can edit the code it is evaluated as you type
Setup
Let’s start by requiring the clojure.spec
namespace:
xxxxxxxxxx
(ns my.spec
(:require [clojure.spec.alpha :as s]))
the evaluation will appear here (soon)...
Predicates
Each spec describes a set of allowed values. There are several ways to build specs and all of them can be composed to build more sophisticated specs.
Any existing Clojure function that takes a single argument and returns a truthy value is a valid predicate spec. We can check whether a particular data value conforms to a spec using conform:
xxxxxxxxxx
(s/conform even? 1000)
xxxxxxxxxx
the evaluation will appear here (soon)...
The conform
function takes something that can be a spec and a data value. Here we are passing a predicate which is implicitly converted into a spec. The return value is “conformed”. Here, the conformed value is the same as the original value - we’ll see later where that starts to deviate. If the value does not conform to the spec, the special value :clojure.spec.alpha/invalid
is returned.
xxxxxxxxxx
(s/conform even? 999)
xxxxxxxxxx
the evaluation will appear here (soon)...
If you don’t want to use the conformed value or check for :clojure.spec.alpha/invalid
, the helper valid?
can be used instead to return a boolean.
xxxxxxxxxx
(s/valid? even? 10)
xxxxxxxxxx
the evaluation will appear here (soon)...
Note that again valid?
implicitly converts the predicate function into a spec. The spec library allows you to leverage all of the functions you already have - there is no special dictionary of predicates. Some more examples:
xxxxxxxxxx
(s/valid? nil? nil)
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? string? "abc")
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? #(> % 5) 10)
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? #(> % 5) 0)
xxxxxxxxxx
the evaluation will appear here (soon)...
Sets can also be used as predicates that match one or more literal values:
xxxxxxxxxx
(s/valid? #{:club :diamond :heart :spade} :club)
xxxxxxxxxx
the evaluation will appear here (soon)...
Registry
Until now, we’ve been using specs directly. However, spec provides a central registry for globally declaring reusable specs. The registry associates a namespaced keyword with a specification. The use of namespaces ensures that we can define reusable non-conflicting specs across libraries or applications.
Specs are registered using def. It’s up to you to register the specification in a namespace that makes sense (typically a namespace you control).
xxxxxxxxxx
(s/def ::date #(instance? js/Date %))
(s/def ::suit #{:club :diamond :heart :spade})
xxxxxxxxxx
the evaluation will appear here (soon)...
A registered spec identifier can be used in place of a spec definition in the operations we’ve seen so far - conform and valid?.
xxxxxxxxxx
(s/valid? ::date (js/Date.))
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/conform ::suit :club)
xxxxxxxxxx
the evaluation will appear here (soon)...
You will see later that registered specs can (and should) be used anywhere we compose specs.
Composing predicates
The simplest way to compose specs is with and and or. Let’s create a spec that combines several predicates into a composite spec with s/and
:
xxxxxxxxxx
(s/def ::big-even (s/and integer? even? #(> % 1000)))
(s/valid? ::big-even :foo)
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? ::big-even 10)
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? ::big-even 100000)
xxxxxxxxxx
the evaluation will appear here (soon)...
We can also use s/or
to specify two alternatives:
xxxxxxxxxx
(s/def ::name-or-id (s/or :name string?
:id integer?))
(s/valid? ::name-or-id "abc")
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? ::name-or-id 100)
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? ::name-or-id :foo)
xxxxxxxxxx
the evaluation will appear here (soon)...
This or
spec is the first case we’ve seen that involves a choice during validity checking. Each choice is annotated with a tag (here, :name
and :id
) and those tags give the branches names that can be used to understand or enrich the data returned from conform and other spec functions.
When an or
is conformed, it returns a vector with the tag name and conformed value:
xxxxxxxxxx
(s/conform ::name-or-id "abc")
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/conform ::name-or-id 100)
xxxxxxxxxx
the evaluation will appear here (soon)...
Many predicates that check an instance’s type do not allow nil
as a valid value (string?
, number?
, keyword?
, etc…). To include nil
as a valid value, use the provided function nilable to make a spec:
xxxxxxxxxx
(s/valid? string? nil)
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/valid? (s/nilable string?) nil)
xxxxxxxxxx
the evaluation will appear here (soon)...
Explain
explain, explain-str and explain-data are another high-level operations in spec
that can be used to report why a value does not conform to a spec. Let’s see it in action with some non-conforming examples we’ve seen so far:
xxxxxxxxxx
(with-out-str
(s/explain ::suit 42))
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/explain-data ::big-even 5)
xxxxxxxxxx
the evaluation will appear here (soon)...
xxxxxxxxxx
(s/explain-str ::name-or-id :foo)
xxxxxxxxxx
the evaluation will appear here (soon)...
The explain
output identifies the problematic value and the predicate it was evaluating. In the last example we see that when there are alternatives, errors across all of the alternatives will be printed.
This is just the beginning of what is possible with spec
. In an upcoming article, we will present more cool features of spec
:
- Sequences
- Entity maps
- Multi-spec
- Collections
- Validations
- Functions
- Macros
- Conformers
Clojurescript rocks!
to stay up-to-date with the coolest interactive articles around the world.
Discover more cool interactive articles about javascript, clojure[script], python, ruby, scheme, c++ and even brainfuck!
Give Klipse a Github star to express how much you appreciate Code Interactivity.
Subscribe to the Klipse newsletter:Feel free to email me [email protected] for getting practical tips and tricks in writing your first interactive blog post.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK