Result, Optional and ControlFlow¶
fp.data provides Result for typed success or failure and ControlFlow for
early termination. Both are non-recursive algebraic data types stored in place,
using the same representation as a user's
Choice. fp.match handles their
constructors and also accepts Mojo's standard Optional directly.
Result¶
Result[T, E] = Ok(T) | Err(E)
Result stores a native Variant[Ok[T], Err[E]]. The constructor wrappers
keep the two cases distinct even when T and E are the same type, which a
plain Variant[T, E] could not do. A payload is reached only after a case check
or through exhaustive elimination: a method, or fp.match with clauses for
Ok[T] and Err[E]. There is no unchecked unwrap and no reinterpreting cast.
Result is Copyable only when both T and E are.
Use Result when failures need to be stored, collected or passed along as
values. Mojo's typed raises remains available for exception propagation, and
the bridges below convert between the two representations explicitly.
Operations¶
Transformations are methods that consume the Result, so they chain. To keep the original, copy it explicitly.
| Method | On Ok(value) |
On Err(error) |
|---|---|---|
r^.map(f) |
Ok(f(value)) |
Err(error), f not called |
r^.flat_map(f) |
f(value), a Result[U, E] |
Err(error), f not called |
r^.map_err(g) |
Ok(value), g not called |
Err(g(error)) |
r^.or_else(g) |
Ok(value), g not called |
g(error), a Result[T, F] |
r.fold(on_ok, on_err) |
on_ok borrows the value |
on_err borrows the error |
r^.fold_owned(on_ok, on_err) |
on_ok receives the value |
on_err receives the error |
r.fold_once / r^.fold_owned_once |
As above, consuming both handlers and calling only the selected one | |
r.is_ok(), r.is_err() |
Case test |
All four methods run through one owned transformation that branches on the
stored case. The algebra's map[ResultFamily[E]] and flat_map[ResultFamily[E]]
call the same methods, so code generic over instances and code that knows it
holds a Result share one implementation. flat_map keeps the stored error type,
so combining different error types needs an explicit map_err. Ok and Err
convert implicitly to a Result whose type is known, so functions write
return Ok(value). map keeps a returned Result as nested data.
Two rules hold for every operation:
- A callback never runs on the inactive case.
- A native error raised by a callback stays a native error. It is never turned
into the Result's
Err, even when the types coincide.
A callback can be a native function or closure, a fixed-arity library callable
such as a partial, or a BorrowCallable for borrowed folds. Folds take
capturing callbacks, which fp.match clauses cannot; a match checks every case
when the program compiles and allows guards. fold_once and fold_owned_once
consume both handler owners, call only the selected one and let native
destruction release the other.
Bridges between raised and stored failures¶
| Bridge | Behavior |
|---|---|
attempt(f, args...) |
Calls f once and returns Ok(result) or Err(error), catching only f's declared error type |
attempt_once(thunk^) |
The same for a consuming OnceThunk, called once |
raise_on_err(r^) / r^.raise_on_err() |
Returns the value or raises the stored error with its original type |
collect_results(iterator) |
Ok(List[T]) of the successes in order, or the first Err; stops pulling at the first Err. It takes a native iterator, such as iter(results^) or a lazy map |
attempt accepts native functions with zero to two positional arguments, with
read, owned or mut conventions, and targets that declare a native keyword pack
var **kwargs: K (homogeneous values or a StringDict[K] expansion), with zero
to two positional prefixes. Mutations a target made before failing remain
visible. The error type must be stated or inferred exactly; attempt never
catches process termination or unrelated failures.
collect_results sequences Results and stops at the first Err; it does not
accumulate validation errors. Empty input gives Ok of an empty list.
Optional¶
fp.match accepts the standard Optional[T] directly. A clause takes T for
a present value, which it borrows, NoneType for an absent one, or Optional[T]
as a catch-all. The standard Optional
owns the value, and its methods (map, and_then, or_else, take) keep
their standard meanings. A generic function cannot name Optional's element
type, so the matcher recognizes the type by name and takes T from the clause
that names it.
ControlFlow¶
ControlFlow[B, C] = Break(B) | Continue(C)
ControlFlow separates intentional early completion from failure. fold_until
steps return it, and every terminal iteration operation uses it internally to
stop (see iteration). It is a Choice of the
constructors Break[B] and Continue[C]: a step returns Break(value) or
Continue(value), which convert to it, and the result is inspected with
isa, indexing, unwrap or fp.match.
Consuming access¶
fp.match only borrows. Consumption stays with the types: Result's
fold_owned, raise_on_err and transformations move the payload out,
Choice.unwrap moves the constructor out, and into_payload moves the value
out of an Ok, Err, Break or Continue. The consuming folds hold each
handler in its own parameter and call only the selected one; the other is
destroyed with the frame. These are plain branches on the stored case, with no
handler objects or context descriptors.