Results
Partly builtresult<'e, 'a> from the standard library, hash-identical to Unison's Either, and Bytes.fromBase16.
stdlib/std.clay declares an ordinary generic variant for operations that can fail with a reason:
type result<'e, 'a> = | Err('e) | Ok('a)
let decode = (hex: bytes): result<string, bytes> => Bytes.fromBase16(hex)
let sizeOf = (hex: bytes): int => switch decode(hex) {
| Ok(b) => Bytes.size(b)
| Err(_) => -1
}It is declared so that it hashes identically to Unison’s Either: Err is Left and Ok is Right, and the header
lists the error parameter first. The order matters. A header with the success parameter first has the same declaration
hash, but swaps the roles of the two constructors, so terms built from it hash differently. That is what lets a
Unison builtin that returns Either Text Bytes be typed as result<string, bytes> with no conversion.
Bytes.fromBase16(b) takes ASCII hex digits (as bytes, the output of Bytes.toBase16) and gives Ok(bytes), or
Err(message) for an odd number of digits (base16: input: invalid length, checked first) or a non-hex digit
(base16: input: invalid encoding at offset: N, N the start of the bad two-digit pair). Upper and lower case are both
accepted. The messages are UCM’s own.
The standard library also has resultIsOk, resultWithDefault and resultMap, for result<string, int> only: Clay
functions cannot take type parameters yet. To use the type, clay add the standard library (or declare result as
above in your own file). For errors with a name, the standard library has failure, which is Unison’s own io2.Failure (same hash):
@unison("io2.Failure") type failure = {kind: typelink, message: string, payload: any}. kind is a type link naming the
kind of failure (typeLink(decodeError)), message is for people, payload is an any (Any.make(x); read it back with
Any.unsafeExtract, which is unsound at the wrong type). Helpers are failureNew, failureText, failurePayload,
failureIsKind, and, for result<failure, int>, resultFlatMap, resultOrElse and resultToOption;
resultFromText(r, kind) lifts a text-error result such as Bytes.fromBase16’s. The carrier result is hash-equal to
Either, so a result<failure, a> is hash-equal to Unison’s Either Failure a. An expected outside failure is a value in the type; a bug is a defect that
aborts the run. Not built: retry and timeout, error unions and tagged errors.
Raising instead of returning
When a caller would rather not thread a result through every step, the standard library has an Exception ability
whose one operation, raise, takes a failure and never returns (it is hash-identical to Unison base’s own
Exception). resultExpect turns an Err into a raise; exceptionToResult is the handler that turns a raise back into
a value, so the two conventions meet at an ordinary handle:
ability Exception {
raise: <'x> failure => 'x
}
let resultExpect<Exception> = (r: result<failure, int>): int =>
switch r {
| Ok(x) => x
| Err(f) => Exception.raise(f)
}resultBug is the third answer for a case that cannot happen: it aborts the run with the failure’s message. A raise is
an effect, so a function that can raise says so in its <Exception> list, and the checker refuses a call that forgets
to handle or declare it.