Let’s start by defining what is a dependent type:

In computer science and logic, a dependent type is a type whose definition depends on a value. A "pair of integers" is a type. A "pair of integers where the second is greater than the first" is a dependent type because of the dependence on the value. — Wikipedia

https://en.wikipedia.org/wiki/Dependent_type

Let’s try to model the types mentioned in the above definition using spec.

Obviously, a "pair of integers" can be modeled as a collection of size 2 containing int in a set order.

We will use an incremental approach:

first model a collection of int then, add a restriction on the size finally check for value ordering

Let’s start by modeling a collection of int . The coll-of function seen in the first post is a good match to specify such a collection.

Remember that its argument is a predicate applied to each element of the collection

( s/def ::ints ( s/coll-of int? )) ( s/valid? ::ints [ 2 "2" ]) ; false ( s/valid? ::ints [ "4" "4" ]) ; false ( s/valid? ::ints [ 3 5 ]) ; true ( s/valid? ::ints [ 2 ]) ; true ( s/valid? ::ints [ 2 2 2 ]) ; true

Now, let’s limit the number of elements to 2 to form a pair. The relevant code is quite straightforward, as the function coll-of allows a :count key to specify the required number of elements:

( s/def ::pair-of-ints ( s/coll-of int? :count 2 )) (1) ( s/valid? ::pair-of-ints [ 2 2 ]) ; true ( s/valid? ::pair-of-ints [ 4 4 ]) ; true ( s/valid? ::pair-of-ints [ 3 5 ]) ; true ( s/valid? ::pair-of-ints [ 2 ]) ; false ( s/valid? ::pair-of-ints [ 2 2 2 ]) ; false

1 Any two int

It’s time to add the final requirement: the second value must be greater than the first. This is clearly a dependent type, as it implies checking on values.

This requires creating the predicate checking for that: ( defn ordered? [ pair ] ( > ( last pair ) ( first pair )))

Then, we need to compose this custom predicate and the out-of-the-box coll-of one. This composition is made possible with the aptly-named boolean macros and and or . ( s/def ::dependent ( s/and ( s/coll-of int? :count 2 ) (1) ordered? )) ( s/valid? ::dependent [ 2 2 ]) ; false ( s/valid? ::dependent [ 3 4 ]) ; true ( s/valid? ::dependent [ 2 1 ]) ; false ( s/valid? ::dependent [ 1 2 3 ]) ; false

That’s it, we have defined a (runtime) dependent type. All building blocks are available to build more complex types.

This is not the end of all, though. Types - whether dependent or not, are not that useful by themselves.