6

Clojure 1.9 introduces clojure.spec: tutorial with live coding examples #cljklip...

 3 years ago
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.
neoserver,ios ssh client

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!

If you enjoy this kind of interactive articles would you consider a (small) donation💸 on Patreon or at least giving a star⭐ for the Klispe repo on Github?

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK