Cardinality and contracts
A cardinality describes how many occurrences a Bag may hold. Repeats count: {1, 2, 2} has three occurrences, even though it has only two different Values.
Count occurrences
Section titled “Count occurrences”count gives the number of occurrences in a Bag:
count({1, 2, 2}) // {3}count(f64:{}) // {0}A cardinality can allow one count, such as 3, or a range, such as 2..5, which includes both ends. 2..<5 leaves out its end, so it allows two to four. * allows any number, including zero.
Contracts
Section titled “Contracts”A contract pairs a Set with a cardinality: which Values a Bag may hold, and how many. After a name, a contract is written after a colon, or with a short marker for a common cardinality:
| Contract | Short marker | Occurrences |
|---|---|---|
x: f64'1 |
x! f64 |
Exactly one |
x: f64'? |
x? f64 |
Zero or one |
x: f64'+ |
x+ f64 |
One or more |
x: f64'* |
x* f64 |
Any number |
x: f64'2..5 |
— | Two to five |
x: f64'2..<5 |
— | Two to four |
In a function’s parameter list, x! f64 takes one Value at a time, while x: f64'1 takes exactly one: see One Value at a time.
State a contract
Section titled “State a contract”def names a computation. SakaQL works out its contract from its expression, or checks a contract you write:
def one! f64 = 1def maybe? str = "red"def flags* bool = {true, false}def few: f64'0..3 = {1, 2}def wrong! f64 = {1, 2} // error SK4301wrong promises exactly one number but computes two.
The Set must fit too:
def wrong_kind! str = 1 // error SK4301A written contract is what other computations see. flags* bool allows any number of Booleans, even though its expression currently gives two.
The Set of a count
Section titled “The Set of a count”count gives an int, and its Set is the cardinality of the Bag it counts. few holds zero to three numbers, so its count is an integer from 0 through 3:
def few_count! int:{0..3} = count(few)def few_count_wrong! int:{1..3} = count(few) // error SK4301A count can meet an f64 in arithmetic, as count(few) * 0.5 does. To give one an f64 contract or compare it with an f64, convert it with f64::from:
def few_count_number! f64 = f64::from(count(few))Name a contract
Section titled “Name a contract”qset names a contract you can reuse:
qset Rating = f64:{1, 2, 3, 4, 5}'?def rating: Rating = 4rating // {4}Rating allows zero or one number from 1 to 5. An empty Bag means there is no rating.
Check an expression’s contract
Section titled “Check an expression’s contract”as gives an expression a broader contract without changing its Values:
{1, 2} as f64'* // {1, 2}expect checks an expression’s Values and its count when it runs. Its target is a contract, and either part may be _ to keep the expression’s own. Here _ keeps the Set, and '1 asks for exactly one occurrence:
"pen" >> {"pen" -> 2, "book" -> 5} expect _'1 // {2}A missing key gives zero occurrences, so this check fails:
"pencil" >> {"pen" -> 2, "book" -> 5} expect _'1 // error: expected exactly one occurrenceexpect keeps every occurrence. It checks the count rather than choosing one Value from several.
Inside a function, the count can be a formula over the function’s parameters, as a result’s contract can (Cardinality formulas). The check uses the formula’s value at the arguments’ counts:
def twice(xs* int) = {xs, xs} expect _'(2 * xs)twice({1, 2}) // {1'2, 2'2}A Set in the target checks each Value too, and the result holds only the Values of both Sets. A function can narrow an int to a u8 this way:
def to_byte(x! int)! u8 = x expect u8'_to_byte(200) // {200}to_byte(300) // error: 300 is outside u8The target’s Set must share a Value with the expression’s, so 300 expect u8'_ is an error before anything runs (SK4301). Where the target holds every Value, nothing is checked: 7 expect int'1 always succeeds. To keep a result within a width without failing, use wrap or saturate (Sets of results).
Next, use Defs to name computations and their steps.