std.contract
apply
Applies a contract to a label and a value.
Type: Contract -> Label -> Dyn -> Dyn
(for technical reasons, this function isn't actually statically typed)
Nickel supports user-defined contracts defined as functions, but also
as records. Moreover, the interpreter performs additional bookkeeping
for error reporting when applying a contract in an expression
value | Contract
. You should not use standard function application
to apply a contract, but this function instead.
Examples
let Nullable = fun param_contract label value =>
if value == null then null
else std.contract.apply param_contract label value
in
let Contract = Nullable {foo | Number} in
({foo = 1} | Contract)
Diagnostics stack
Using apply
will stack the current custom error reporting data, and
create a fresh working diagnostic. apply
thus acts as a split point
between a contract and its subcontracts, providing the subcontracts
with a fresh diagnostic to use, while remembering the previous
diagnostics set by parent contracts.
Illustration
let ChildContract = fun label value =>
label
|> std.contract.label.with_message "child's message"
|> std.contract.label.append_note "child's note"
|> std.contract.blame
in
let ParentContract = fun label value =>
let label =
label
|> std.contract.label.with_message "parent's message"
|> std.contract.label.append_note "parent's note"
in
std.contract.apply ChildContract label value
in
null | ParentContract
This example will print two diagnostics: the main one, using the message and note of the child contract, and a secondary diagnostic, using the parent contract message and note.
blame
Raises blame for a given label.
Type: forall a. Label -> a
(for technical reasons, this function isn't actually statically typed)
Blame is the mechanism to signal contract violation in Nickel. It ends the program execution and prints a detailed report thanks to the information tracked inside the label.
Examples
IsZero = fun label value =>
if value == 0 then
value
else
std.contract.blame label
blame_with_message
Raises blame with respect to a given label with a custom error message.
Type: forall a. String -> Label -> a
(for technical reasons, this function isn't actually statically typed)
Same as blame
, but takes an additional error message that will be
displayed as part of the blame error. blame_with_message message label
is equivalent to blame (label.with_message message label)
Examples
let IsZero = fun label value =>
if value == 0 then
value
else
std.contract.blame_with_message "Not zero" label
in
0 | IsZero
Equal
Equal
is a contract enforcing equality to a given constant.
Equal
is lazy over arrays and records. When checking such values,
Equal
doesn't test for equality of elements right away (it just tests
that the size is the same for arrays and that fields are the same for
records), but returns a new value where equality subcontracts have been
pushed inside each element.
Example
1 + 4 | std.contract.Equal 5
=> 5
4 | std.contract.Equal 5
=> error: contract broken by a value
from_predicate
Generates a contract from a boolean predicate.
Type: (Dyn -> Bool) -> (Label -> Dyn -> Dyn)
(for technical reasons, this function isn't actually statically typed)
Examples
let IsZero = std.contract.from_predicate (fun x => x == 0) in
0 | IsZero
label
The label submodule provides functions that manipulate the label associated with a contract application.
A label is a special opaque value automatically passed by the Nickel interpreter to contracts when performing a contract check.
A label stores a stack of custom error diagnostics, that can be manipulated by the functions in this module. Labels thus offer a way to customize the error message that will be shown if a contract is broken.
The setters (with_XXX
functions) always operate on the current error
diagnostic, which is the last diagnotic on the stack. If the stack is
empty, a fresh diagnostic is silently created when using a setter.
The previous diagnostics on the stack are considered archived, and can't be modified anymore. All diagnostics will be shown during error reporting, with the most recent being used as the main one.
std.contract.apply
is the operation that pushes a new fresh diagnostic on
the stack, saving the previously current error diagnostic. The function
std.contract.apply
is typically used to apply subcontracts inside a parent
contract. Stacking the current diagnostic potentially customized by
the parent contract saves the information inside, and provides a fresh
diagnostic for the child contract to use.
label.append_note
Appends a note to the error notes of the current diagnostic of a label.
Type: String -> Label -> Label
(for technical reasons, this function isn't actually statically typed)
Examples
let AlwaysFailWithNotes = fun label _value =>
label
|> std.contract.label.append_note "This is note 1"
|> std.contract.label.append_note "This is note 2"
|> std.contract.blame
in
null | AlwaysFailWithNotes
label.with_message
Attaches a custom error message to the current error diagnostic of a label.
Type: String -> Label -> Label
(for technical reasons, this function isn't actually statically typed)
If a custom error message was previously set, there are two possibilities:
- the label has gone through a
std.contract.apply
call in-between. In this case, the previous diagnostic has been stacked, and usingwith_message
won't erase anything but rather modify the fresh current diagnostic. - no
std.contract.apply
has taken place since the last message was set. In this case, the current diagnostic's error message is replaced.
Examples
let ContractNum = std.contract.from_predicate (fun x => x > 0 && x < 50) in
let Contract = fun label value =>
if std.is_number value then
std.contract.apply
ContractNum
(std.contract.label.with_message
"num subcontract failed! (out of bound)"
label
)
value
else
value
in
5 | Contract
label.with_notes
Attaches custom error notes to the current error diagnostic of a label.
Type: Array String -> Label -> Label
(for technical reasons, this function isn't actually statically typed)
If custom error notes were previously set, there are two possibilities:
- the label has gone through a
std.contract.apply
call in-between. In this case, the previous diagnostic has been stacked, and usingwith_notes
won't erase anything but rather modify the fresh current diagnostic. - no
std.contract.apply
has taken place since the last message was set. In this case, the current diagnostic's error notes will be replaced.
Examples
let ContractNum = std.contract.from_predicate (fun x => x > 0 && x < 50) in
let Contract = fun label value =>
if std.is_number value then
std.contract.apply
ContractNum
(label
|> std.contract.label.with_message "num subcontract failed! (out of bound)"
|> std.contract.label.with_notes [
"The value was a number, but this number is outside the expected bounds",
"The value must be a number between 0 and 50, both excluded",
]
)
value
else
value
in
5 | Contract
Sequence
Apply multiple contracts from left to right.
Type: Array Contract -> Contract
(for technical reasons, this function isn't actually statically typed)
Examples
x | std.contract.Sequence [ Foo, Bar ]
is equivalent to
(x | Foo) | Bar
This is useful in positions where one cannot apply multiple contracts, such as an argument to another contract:
x | Array (std.contract.Sequence [ Foo, Bar ])
Or stored in a variable:
let C = std.contract.Sequence [ Foo, Bar ] in
x | C
unstable
The unstable module gathers contracts that are used right now in the standard library to check additional pre-condition, but aren't yet stabilized.
Unstable contracts are useful as a temporary solution for argument validation, but they shouldn't be used outside of the standard library. They aren't part of the backward compatibility policy, and are subject to change at any point in time.
unstable.ArraySliceFun
Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.
A function contract which checks that the three first arguments
are, in order, two numbers and an array such that the two numbers
are a valid slice of that array, that is they are natural numbers
such that: 0 <= n1 <= n2 <= length array
This contract blames only if the arguments are of the right type but don't satisfy the tested condition. The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a string, this contract silently passes.
unstable.DependentFun
Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.
A dependent function contract, where the argument is also passed to the contract of the returned value.
That is, some_function | DependentFun Foo Bar
performs the
same checks as
fun arg =>
let arg_checked = (arg | Foo) in
(some_function arg_checked | (Bar arg_checked))
unstable.HasField
Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.
A function contract for 2-ary functions which checks that second argument is a record that contains the first argument as a field. This contract is parametrized by the return type of the function.
This contract blames only if the arguments are of the right type but don't satisfy the tested condition (the first is a string and the second is a record). The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a number, this contract silently passes.
Example
let f | HasField Dyn = fun field record => record."%{field}" in
(f "foo" { foo = 1, bar = 2 })
+ (f "baz" { foo = 1, bar = 2 })
In this example, the first call to f
won't blame, but the second
will, as baz
isn't a field of the record argument.
unstable.IndexedArrayFun | [| 'Index, 'Split |] -> Dyn -> Dyn
Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.
A function contract which checks that the two first argument are,
in order, a number and an array such that the number is a valid
index for that array, that is a natural number such that
0 <= index <= length array
.
This contract blames only if the arguments are of the right type but don't satisfy the tested condition. The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a string, this contract silently passes.
unstable.PosNumber
Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.
A contract for a positive number.
This contract blames only if the value is a number but is not positive: the fact that the value is a number is assumed to be checked by an associated static type annotation.
unstable.RangeFun
Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.
A function contract which checks that the two first argument are a
valid range, that is two numbers start
and end
such that
start <= end
.
This contract blames only if the arguments are of the right type but don't satisfy the tested condition. The type of arguments is assumed to be checked by an associated static type annotation. For example, if the first argument is a string, this contract silently passes.
unstable.RangeStep
Warning: this is an unstable item. It might be renamed, modified or deleted in any subsequent minor Nickel version.
A contract for a range step, which must be a positive number.
Mostly a wrapper around std.contract.unstable.PosNumber
with a
custom error message.
This contract blames only if the value is a number but is not positive: the fact that the value is a number is assumed to be checked by an associated static type annotation.