structural-typing.assist.special-words
There are a number of symbols that have special meanings. They are aggregated here for convenience.
ALL
When included in a path, expects a collection and applies the rest of the path to each element.
each-of
Use each-of
to describe a “forking” path. This is convenient when two parts of a bigger data structure should be built the same way.
(type! :Plat {[:corners (each-of :nw :ne :sw :se)] (includes :GeoPoint)})
through-each is a synonym. I tend to use each-of
for the end of the path, through-each
for a fork earlier than that.
explain-with
(explain-with explainer predicate)
After the predicate
fails, the failure will need to be explained. Arrange for explainer
to be called with the oopsie that results from the failure.
(explain-with #(format "Yo! %s has %s characters, which is WAY too long."
(:leaf-value %)
(count (:leaf-value %)))
#(< (count %) 8)))
includes
(includes type-signifier)
not-nil
(not-nil & args)
WARNING: #’structural-typing.guts.preds.pseudopreds/not-nil is deprecated. Deprecated in favor of required-path
, reject-nil
, or reject-missing
.
paths-of
(paths-of type-signifier-or-map)
Include all the paths of a type (or a literal map) within a path.
(type! :StrictX (includes :X)
(requires (paths-of :X)))
The above example constructs a stricter version of :X
by insisting all of its paths are required.
When the argument is a map, it is flattened before the paths are extracted, so that {:a {:b even?}}
and {[:a :b] even?}
have the same effect. (Included types are already flat.)
RANGE
(RANGE inclusive-start exclusive-end)
Use this in a path to select a range of values in a collection. The first argument is inclusive; the second exclusive.
(type! :ELEMENTS-1-AND-2-ARE-EVEN {[(RANGE 1 3)] even?})
reject-missing
This appears in a predicate list, but it is never called directly. Its appearance means that cases like the following are rejected:
user=> (type! :X {:a [string? reject-missing]})
user=> (built-like :X {})
:a does not exist
user=> (type! :X {[(RANGE 0 3)] [reject-missing even?]})
user=> (built-like :X [])
[0] does not exist
[1] does not exist
[2] does not exist
See also reject-nil and required-path.
reject-nil
False iff the value given is nil
. By default, type descriptions allow nil values, following Clojure’s lead. To reject nils, use type descriptions like this:
(type! :X {:a [reject-nil string?]})
… or, when checking types directly:
(built-like [string? reject-nil] nil)
See also reject-missing and required-path.
required-path
False iff a key/path does not exist or has value nil
. See also reject-missing and reject-nil.
requires
(requires & args)
Often, all you want to say about some parts of a type is that they’re required. requires
is a shorthand way to do that.
(type! :Point (requires :x :y))
(type! :Line (requires [:start :x] [:start :y]))
(type! :Line (requires [(through-each :start :end) (:each-of :x :y)]))
requires-mentioned-paths
(requires-mentioned-paths & condensed-type-descriptions)
Canonicalizes the type descriptions into a single path->pred map and adds required-path to each path’s predicates.
(type! :X (requires-mentioned-paths (includes :Point)
{:color rgb-string?}))
Note: It can’t require paths you don’t mention. The easiest way to mention a path is to name it in a requires
- which may be either an argument to this function or outside it:
(type! :X (requires-mentioned-paths (requires :name)
(includes :Point)))
(type! :X (requires :name)
(requires-mentioned-paths (includes :Point)))
show-as
(show-as name predicate)
Associate the given name
string with the predicate for use when predicate failures are explained.
(show-as "less than 3" (partial >= 3))
through-each
(through-each & alternatives)
Use through-each
to describe a “forking” path. This is convenient when two parts of a bigger data structure should be built the same way.
(type! :Line {[(through-each :start :end) :location] (includes :Point)})
each-of is a synonym. I tend to use each-of
for the end of the path, through-each
for a fork earlier than that.