Quantification
Slang Contracts include bounded quantification expressions and these can be used to state properties/constraints in assertions as well as other Slang Contracts constructs. Slang also has unbounded quantification expressions that can be used in proof contexts, but in Slang Contracts quantification is always bounded.
Quantification expressions are most often used when reasoning about values of
integer ranges and Slang collections including sequences, sets, and
maps. Technically, Slang’s bounded quantification can only be applied
to integer ranges and sequence elements (both for immutable and
mutable sequences), but one can the elements
method for sets and
keys
for maps to obtain sequences that can quantified over.
Quantification over Integer Ranges
The most common bounded quantification is over integer (Z
) ranges, and
the general form for these expressions is as follows::
*q* (*m* until/to *n*) (*i* => ... *i* ...)
where
- q is a quantifier
All
orExists
, - m is an expression of type
Z
and is first element of the range to quantify over - n is an expression of type
Z
and is the upper bound of the range, to
indicates that the range is a closed interval (includes n) where wasuntil
indicates that the range goes up to but does not include n,- i is the quantified (bound) variable, and
- “… i …” is a boolean expression containing i.
Given the following sequence of integers::
val s1 = ISZ(1,2,3,4)
one can use quantified expressions to assert properties of the sequence::
assert(All(0 until s1.size)(i => s1(i) >= 0))
assert(Exists(0 until s1.size)(j => s1(j) == 2))
assert(Exists(0 until s1.size)(j => s1(j) < 0)) // fails
The upper and lower bounds of the range can be expressions, which is useful for reasoning about segments of a sequence::
assert(All(0 to ((s1.size / 2) - 1))(k => s1(k) > 0 & s1(k) < 3))
assert(All((s1.size / 2) until s1.size))(k => s1(k) >= 3))
Tip
Note: Following the data structure-based representation of Slang Contracts,
All
and Exists
are actually object constructors where the
first argument in the examples above is a sequence of Z
representing the range, and the second argument is a lambda expression
... => ...
that introduces the quantified variable as the argument
of the lambda expression and has a body of boolean type.
Slang’s bounded quantification expressions can be used in
specification contexts and proof contexts.
Since the assertion specification is also executable,
quantification expressions must be executable.
To get an executable form of a quantification expression,
the Slang compiler transforms the expression into a loop.
For example, the following pseudo-code shows the structure of the loop
implementation of the All
quantifier when it is applied to ranges
of type Z
::
def all(seq: ISZ[Z])(p: Z => Boolean): B = {
for (e <- seq) {
if (!p(e)) {
return F
}
}
return T
}
When quantifying over sequence indices, Slang provides some syntactic
sugar than enables one to use the indices
method inspired from Scala collections libraries and write::
assert(All(s.indices)(i => s(i) >= 0))
Behind the scenes, this gets represented as something equivalent to::
assert(All(0 until s1.size)(i => s1(i) >= 0))
Quantifiers can be nested, as illustrated in the following examples::
val s3 = ISZ(ISZ(1,2,3),ISZ(11,12,13),ISZ(21,22,23))
// illustrating nested quantification
assert(All(s3.indices)(i => All(s3(i).indices)(j => s3(i)(j) > i * 10)))
assert(Exists(s3.indices)(i => All(s3(i).indices)(j => (s3(i)(j) >= 11) & (s3(i)(j) <= 13))))
assert(Exists(s3.indices)(i => Exists(s3(i).indices)(j => (s3(i)(j) == 12))))
In the first example, with zero-based indexing, the assertion uses
nested All
to check
that successive sub-sequences have values greater than 0 (0 * 10), 10
(1 * 10), and 20 (2 * 10), respectively.
The second example uses an existential
quantification to state that there exists a sub-sequence with an index
i
where all the index positions j
of the sub-sequence have
values between 11 and 13.
The third example, states that there
exists a sub-sequence with index i
such that there exists a index
j
within the sub-sequence where the value is 12.
Tip
Note: In the current syntax of Slang, one cannot mimic familiar
abbreviations from logic notation such as Ax,y. … x .. y …, that
is, one cannot write something like All(0 to 5, 10 to 15)((x,y) => ...)
.
Quantification over Sequences Elements
Slang also provides bounded quantification over sequence elements,
enabled by various forms of syntactic sugar. For example, one can
state constraints of sequence elements directly (as opposed to
“looking up” the element v
at a particular index i
via
s1(i)
) as follows::
assert(All(s1)(v => v >= 0)) // direct quantification over sequence elements
Behind the scenes, this gets represented as something equivalent to::
assert(All(0 until s1.size)(i => (v => v >= 0) s1(i))
which in turn is equivalent to::
assert(All(0 until s1.size)(i => s1(i) >= 0))
This style of quantification also applies to sequences of pairs/tuples (Robby: couldn’t get this to work)::
// assert(All(s2)((v1,v2) => v1 == v2))
Slang doesn’t (yet) provide bounded quantification over set and map elements, but one can use associated methods that yield sequences.
For sets, the elements
method can be used::
val set1: Set[Z] = Set.empty[Z] ++ ISZ(1,2,3,4)
// state a constraint that applies to all elements of a set
assert(All(set1.elements)(v => v < 5))
For maps, both the keys
and values
methods can be used::
val m: Map[C,Z] = Map[C,Z](ISZ(('a',1),('b',2),('c',3)))
assert(All(m.keys)(c => c < 'd'))
assert(All(m.values)(n => n < 4))
Robby: I couldn’t get quantification over entries
to work.
Robby: Help me with explaining the desugarings for the above, e.g., as a numerical range over the size of the key sets.
Using the sequence element quantification notation, the examples of nested quantification above can be re-stated more concisely::
// rephrasing the nested quantification examples above, in a more compact form
// by quantifying directly over sequence elements (when possible)
// original: (All(s3.indices)(i => All(s3(i).indices)(j => s3(i)(j) > i * 10)))
assert(All(s3.indices)(i => All(s3(i))(v => v > i * 10)))
// original: assert(Exists(s3.indices)(i => All(s3(i).indices)(j => (s3(i)(j) >= 11) & (s3(i)(j) <= 13))))
assert(Exists(s3)(ss => All(ss)(v => (v >= 11) & (v <= 13))))
// original: assert(Exists(s3.indices)(i => Exists(s3(i).indices)(j => (s3(i)(j) == 12))))
assert(Exists(s3)(ss => Exists(ss)(v => v == 12)))
In the first example, the first quantication All(s3.indices)(i => ...)
cannot be restated because the index i
is used in a
calculation. However, second quantification can now be stated
directly over values v
in the subsequence s(i)
.
The second example can now be understood as there exists a
sub-sequence ss
such that all values v
of the sub-sequence lie
between and 13.
The third example can now be understood as there exists a
sub-sequence ss
such that there exists a value v
of the
sub-sequence that equals 12.