-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspec-intro.tex
104 lines (82 loc) · 4.25 KB
/
spec-intro.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
\chapter{Introduction to clojure.spec}
Recently, Clojure added a runtime verification system to its core library called
\texttt{clojure.spec}.
It resembles common approaches to runtime verification, such as Racket's contract
system, but is different in several important ways.
Firstly, \texttt{clojure.spec} is designed to treat most values as ``data at rest''. That is,
at verification sites, values are eagerly traversed without waiting to see
if or how the program actually uses them.
When we consider that \texttt{clojure.spec} treats infinite streams
and functions as data at rest, we begin to see the tradeoffs that have been
made.
Secondly, specifications (called ``specs'') are not enforced by default. Users must
opt-in to enforcing specs via an explicit instrumentation phase.
This is also different than most contract systems, many of which are enforced
by default. There is no standard way to integrate spec enforcement into a
test suite, so it is difficult to tell whether specific specs are primarily
unchecked documentation, or actually used for runtime verification.
Since \texttt{clojure.spec} has a unique feature set amongst runtime verification
libraries, it is interesting to consider how programmers use \texttt{clojure.spec}
in practice. For example, do programmers find the semantics of treating functions
as data at rest useful?
Unfortunately since specs are opt-in, it is difficult to
correlate someone writing a spec with that person \emph{using} the spec,
implying spec's semantics as being useful.
Nevertheless, in the following sections we attempt to draw conclusions about
spec's common usage based mostly on the frequency of spec annotations.
\section{Function specifications in clojure.spec}
From here, we map the namespace prefix \texttt{s} to \texttt{clojure.spec.alpha},
and \texttt{stest} to \texttt{clojure.spec.test.alpha}.
\begin{verbatim}
(require '[clojure.spec.alpha :as s])
(require '[clojure.spec.test.alpha :as stest])
\end{verbatim}
There are two kinds of function checking semantics in \texttt{clojure.spec}.
We use \texttt{intmap}, a higher-order function that maps a function over
a collection of ints, to demonstrate both semantics.
\begin{verbatim}
(defn intmap
"Maps a collection of ints over a function."
[f c]
(map f c))
\end{verbatim}
If the programmer wants to write a higher-order function spec to
verify \texttt{intmap}, they might write the following spec.
\begin{verbatim}
(s/fdef intmap
:args (s/cat :f (s/fspec :args (cat :x int?) :ret int?)
:c (s/coll-of int?))
:ret (s/coll-of int?))
\end{verbatim}
The \texttt{s/fdef} form signals we are annotating a top-level
function, in this case \texttt{intmap}. Argument specs are
provided with the \texttt{:args} keyword option
in the form of the ``tagged'' heterogeneous collection spec
\texttt{s/cat}---here 2 arguments are allowed, tagged as
\texttt{:f} for the function and \texttt{:c} as the collection.
The \texttt{s/fspec} spec is another kind of function spec,
specifically for non-top-level functions (such as function arguments
to top-level functions). It has a similar syntax to \texttt{s/fdef},
but a function name is not provided.
In a nutshell, \texttt{s/fdef} provides traditional proxy-based
verification semantics while \texttt{s/fspec} uses eager \emph{generative testing}
to exercise a function before letting it pass the spec boundary, bare (without a proxy).
We will now demonstrate how the following call gets checked.
\begin{verbatim}
(intmap inc [1 2 3])
;=> (2 3 4)
\end{verbatim}
First, the programmer instruments \texttt{intmap} with:
\begin{verbatim}
(stest/instrument `intmap)
\end{verbatim}
This mutates the top-level binding associated with \texttt{intmap}, wrapping a function
proxy around the original value.
Now, when checking \texttt{(intmap inc [1 2 3])}, the \texttt{inc} function is
called several hundred times with generated values conforming to \texttt{int?},
and checks each call returns an \texttt{int?}.
Then, \texttt{[1 2 3]} is eagerly checked against \texttt{(s/coll-of int?)}.
The original \texttt{intmap} function is then called with the original arguments,
yielding a value \texttt{(2 3 4)}. Instrumentation does not check return value specs,
so \texttt{(s/coll-of int?)} is ignored, and the original return value is passed to the calling
context.