Skip to main content

Lists

Caesar provides a built-in list type []T where T is the element type. Lists are array-like values with an associated length.

Use []T for index-based algorithms. If you need constructors such as cons, head/tail recursion, or structural induction, define your own datatype with domains.

Operations

len

func len(list: []T): UInt

Returns the length of list.

select

func select(list: []T, index: UInt): T

Returns the element at index.

This operation is only specified for indices with index < len(list). For indices outside that range, do not rely on the result.

store

func store(list: []T, index: UInt, value: T): []T

Returns a list obtained from list by replacing the element at index with value.

This operation is only specified for indices with index < len(list). For indices outside that range, do not rely on the result.

store preserves the length of the list.

Equality

Lists support == and !=.

Built-in list equality is encoded extensionally in the SMT backend. In the backend, the corresponding quantified formula is named list_eq. Two lists are equal if they have the same length and the same elements at all valid indices.

len(a) == len(b) &&
forall i: UInt. (i < len(a)) ==> (select(a, i) == select(b, i))

This extensionality encoding is quantified, so it can make SMT proofs more brittle.

Examples

The built-in API is enough for many length-preserving transformations. For example, fill rewrites every valid index with the same value:

proc fill(list: []UInt, value: UInt) -> (res: []UInt)
post ?(len(res) == len(list))
post ?(forall k: UInt. (k < len(list)) ==> (select(res, k) == value))
{
res = list
var i: UInt = 0
@invariant(?(
i <= len(list) &&
len(res) == len(list) &&
(forall k: UInt. (k < i) ==> (select(res, k) == value))
))
while (i < len(list)) {
res = store(res, i, value)
i = i + 1
}
}

Operations that change the length are different. The built-in API does not provide a constructor for a fresh longer list, so append and concat are currently best written as specification-only procedures:

proc append(list: []UInt, value: UInt) -> (res: []UInt)
post ?(len(res) == len(list) + 1)
post ?(select(res, len(list)) == value)
post ?(forall k: UInt. (k < len(list)) ==> (select(res, k) == select(list, k)))

proc concat(left: []UInt, right: []UInt) -> (res: []UInt)
post ?(len(res) == len(left) + len(right))
post ?(forall k: UInt. (k < len(left)) ==> (select(res, k) == select(left, k)))
post ?(forall k: UInt. (k < len(right)) ==> (select(res, len(left) + k) == select(right, k)))

More checked examples are in tests/boolean/procs/list_ops.heyvl.

Remarks

Built-in lists are encoded as an SMT array together with a length. Bounds such as i < len(list) should usually be stated explicitly in specifications.

Counterexamples print lists in a compact form such as [4, 10] (len=2, oob: default=4, -1 -> 12). The bracketed part shows the in-bounds elements. The optional oob part shows values that the SMT model assigns outside the valid range 0 .. len(list) - 1; these are not part of the specified list contents. Longer lists may be truncated to [a, b, ...] (len=n, ...).

Many list properties are quantified. For nontrivial quantified proofs, triggers may matter. See debugging quantifier instantiations for details.

The built-in []T type is intended for index-based reasoning. If you need a recursive list datatype, define one with domains.