View Source Bond.Predicates (Bond v1.7.0)

Functions, operators, and quantifiers for building the boolean expressions used in contracts and assertions.

Despite the name, this module is broader than a list of predicates: it provides the toolkit for constructing predicate expressions — boolean-valued helper functions (xor/2, implies?/2), logical connectives (|||, ~>), a pattern-match operator (<~), and the quantifiers forall/2 and exists/2. Every construct here evaluates to a boolean, so each can stand on its own as the predicate of an assertion or be combined into a larger one.

This module is automatically imported for all assertion expressions, specifically in preconditions defined with @pre, postconditions defined with @post, and in uses of Bond.check/1.

To use the infix operator versions of the predicates in other contexts, this module must be imported in the using module.

Operator precedence

~> (implication) and <~ (pattern match) share the same precedence and left-associate. This matters when both appear in a single assertion. The natural-language reading "if x > 0 then the result matches {:ok, _}"

# WRONG — fails to compile
@post (x > 0) ~> {:ok, _} <~ result

parses as ((x > 0) ~> {:ok, _}) <~ result, where the LHS of <~ is an arbitrary expression containing _. _ isn't a valid value position, so compilation fails with a cryptic error.

Add explicit parens around the inner operator to get the intended grouping:

# Right
@post (x > 0) ~> ({:ok, _} <~ result)

Rule of thumb: any time you nest <~ inside ~> (or vice versa), parenthesize the inner expression.

Summary

Functions

Pattern matching operator: equivalent to match?(pattern, expression).

Existential quantifier: asserts that a predicate holds for at least one element of an enumerable.

Universal quantifier: asserts that a predicate holds for every element of an enumerable.

Logical implication: does p imply q?

Logical exclusive or: is either p or q true, but not both?

Logical exclusive or operator: p ||| q means xor(p, q).

Logical implication operator: p ~> q is equivalent to not p or q, with short-circuit evaluationq is only evaluated when p is truthy.

Functions

Link to this macro

pattern <~ expression

View Source (macro)

Pattern matching operator: equivalent to match?(pattern, expression).

Examples

iex> {:ok, %Date{}} <~ Date.new(1974, 6, 6)
true
iex> {:error, _} <~ Date.new(-1, -1, -1)
true
Link to this macro

exists(generator, predicate)

View Source (macro)

Existential quantifier: asserts that a predicate holds for at least one element of an enumerable.

Written exists(pattern <- enumerable, predicate) — "there exists a pattern in enumerable such that predicate". Equivalent in truth value to Enum.any?(enumerable, fn pattern -> predicate end). Evaluation short-circuits at the first satisfying element.

Unlike forall/2, a failure has no single offending element, so the diagnostic reports that no element satisfied the predicate, along with the number of elements checked:

precondition has_admin failed for call to M.authorize/1
|   assertion: exists(u <- users, u.role == :admin)
|   counterexample: no element of `users` satisfies `u.role == :admin` (3 elements)

An empty enumerable is false (no witness exists).

Examples

iex> import Bond.Predicates
iex> exists(x <- [1, 2, 3], x > 2)
true
iex> exists(x <- [1, 2, 3], x > 5)
false
iex> exists(x <- [], x > 0)
false
Link to this macro

exists(a, b, c)

View Source (macro)
Link to this macro

exists(a, b, c, d)

View Source (macro)
Link to this macro

forall(generator, predicate)

View Source (macro)

Universal quantifier: asserts that a predicate holds for every element of an enumerable.

Written with comprehension-style generator syntax — forall(pattern <- enumerable, predicate) — it reads "for all pattern in enumerable, predicate". It is equivalent in truth value to Enum.all?(enumerable, fn pattern -> predicate end), but on failure Bond reports which element violated the predicate and its zero-based index, rather than only that the whole expression was false:

precondition all_positive failed for call to M.scale/1
|   assertion: forall(x <- items, x > 0)
|   counterexample: element at index 3 (-2) does not satisfy `x > 0`

Evaluation short-circuits at the first violating element. An empty enumerable is vacuously true. A predicate that raises (rather than returning falsy) propagates — guard shape-dependent predicates with ~>, exactly as in multi-clause contracts.

forall/exists return ordinary booleans, so they compose with and, or, not, ~>, and |||. When several quantifiers appear in one assertion (including nested ones), the element-level detail reflects the last quantifier to fail; for a bare quantifier it is exact.

Examples

iex> import Bond.Predicates
iex> forall(x <- [1, 2, 3], x > 0)
true
iex> forall(x <- [1, -2, 3], x > 0)
false
iex> forall(x <- [], x > 0)
true
Link to this macro

forall(a, b, c)

View Source (macro)
Link to this macro

forall(a, b, c, d)

View Source (macro)
@spec implies?(as_boolean(term()), as_boolean(term())) :: boolean()

Logical implication: does p imply q?

For an infix operator version of logical implication see ~>/2.

Examples

iex> implies?(true, true)
true
iex> implies?(true, false)
false
iex> implies?(false, true)
true
iex> implies?(false, false)
true
@spec xor(as_boolean(term()), as_boolean(term())) :: boolean()

Logical exclusive or: is either p or q true, but not both?

For an infix operator version of exclusive or see |||/2.

Examples

iex> xor(true, true)
false
iex> xor(true, false)
true
iex> xor(false, true)
true
iex> xor(false, false)
false

Logical exclusive or operator: p ||| q means xor(p, q).

Note that the ||| operator has higher precedence than many other operators and it may be necessary to parenthesize the expressions on either side of the operator to get the expected result.

Examples

iex> true ||| true
false
iex> true ||| false
true
iex> false ||| true
true
iex> false ||| false
false
iex> x = 2
2
iex> y = 4
4
iex> (x - y < 0) ||| (y <= x)
true

Logical implication operator: p ~> q is equivalent to not p or q, with short-circuit evaluationq is only evaluated when p is truthy.

This makes ~> safe for shape-dependent assertions where the consequent would otherwise raise on certain inputs to the antecedent. For example:

@pre is_binary(x) ~> String.length(x) > 0

reads "if x is a binary, then its length is positive." With the short-circuit, String.length(x) is never called when x isn't a binary (which would raise FunctionClauseError). This pattern is the canonical way to express clause-specific assertions on multi-clause functions where contracts must apply uniformly to every clause.

Note that the ~> operator has higher precedence than many other operators and it may be necessary to parenthesize the expressions on either side of the operator to get the expected result. See the "Operator precedence" section above.

Examples

iex> true ~> true
true
iex> true ~> false
false
iex> false ~> true
true
iex> false ~> false
true
iex> x = 2
2
iex> y = 4
4
iex> (x - y < 0) ~> (y > x)
true
iex> false ~> raise("not evaluated — antecedent is false")
true