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)

During creation of a type by named or type!, a call to includes is replaced with the type the type-signifier refers to.

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.