std/core/result
std/core/src/result.trb
Failure as a value: Result, its two cases, and the vocabulary that works on the value of a successful one.
There are no exceptions in the language. A function that can fail says so in its result type, ? passes a failure
up to the caller, and a failure that nobody handles is therefore impossible to overlook. panic is the other half
of the story and belongs to bugs, not to failures a program can answer.
Related
Option- absence without a reason.Show- what an error type implements so thatResult.expectcan print it.
type Result
type Result<Value, Failure> with OrElse<Value>
The result of an operation that can fail: Ok with the value, or Fail with the reason.
Reach for it wherever failure is part of the answer and not a bug: reading a file, parsing text, talking to a
server. The postfix ? unwraps an Ok and returns the Fail from the surrounding function, converting the error
type through From on the way, so a chain of fallible steps reads like a chain of ordinary ones.
map, flatMap and forEach mean the same as everywhere else (see "One Vocabulary" in CONCEPT.md), work on the
Ok side, and run immediately - a Result is a value, not a pipeline.
? is what a caller writes instead of unwrapping the value: quarter below answers with the failure of the step
that failed, and says nothing about it.
Examples
fn half(value: Int): Result<Int, String> {
if value % 2 == 0 { Ok(value / 2) } else { Fail "an odd number has no half" }
}
fn quarter(value: Int): Result<Int, String> {
const once = half(value)?
half once
}
print half(3)
print quarter(8)
Pitfalls
?needs a surrounding function whose result is a Result. At the top level of a script there is nothing to return to, so a script answers a failure withResult.orElseor with amatch.
Open
There is no counterpart of Result.flatMap for the failing side: recovering from a Fail with another operation
that can fail itself is written with a match.
Related
Option- the answer when the absence needs no reason.Result.okconverts one into the other.Result.mapError- what a?between two error types does by itself.
case Ok
case Ok(value: Value)
The operation worked, and this is what it produced.
case Fail
case Fail(error: Failure)
The operation failed, and this is why.
fn isOk
fn isOk(): Bool
Whether the operation worked. For the value itself, match or ? instead.
fn isError
fn isError(): Bool
Whether the operation failed.
fn map
fn map<Output>(transform: Transform<Value, Output>): Result<Output, Failure>
Changes the value of an Ok and leaves a Fail alone: one more step on the way that worked.
fn mapError
fn mapError<OtherFailure>(transform: (error: Failure) => OtherFailure): Result<Value, OtherFailure>
Changes the reason of a Fail and leaves an Ok alone: for a caller that answers with an error type of its own.
? does this by itself wherever the two error types are connected by From. This is the way to write down what
happened where they are not.
Examples
const parsed = Int.tryFrom "nine"
print parsed.mapError({ _ => "nine is not a number" })
fn flatMap
fn flatMap<Output>(transform: Transform<Value, Result<Output, Failure>>): Result<Output, Failure>
Continues with a step that can fail itself and keeps one level: the explicit form of what ? does.
fn forEach
fn forEach(action: Action<Value>)
Runs the action if the operation worked: the statement form of Result.map.
fn toList
fn toList(): List<Value>
Into the world of pipelines: a list with the value of an Ok, and an empty one for a Fail.
fn ok
fn ok(): Value?
The value as an Option, which drops the reason: for a caller that only wants to know whether there is one.
fn orElse
fn orElse(fallback: lazy Value): Value
The value, or the fallback if the operation failed. The way out of the type where the reason does not matter.
The fallback is lazy, so it is only evaluated when it is needed.
Examples
const parsed = Int.tryFrom "nine"
print parsed.orElse(0)
fn expect
fn expect(message: String): Value where Failure: Show
The value, with the message and the reason if the operation failed. For a place where a failure is a bug.
Panics
When the result is a Fail. The panic prints the message and the error, so the message says what was expected of
the operation, not that it failed.
extend Result<Value, Failure> with Show
extend<Value: Show, Failure: Show> Result<Value, Failure> with Show
Ok(value) or Fail(failure).
fn show
fn show(): String
extend Result<Target, Failure> with From<Iterate<Result<Value, Failure>>>
extend< Value, Failure, Target: From<Iterate<Value>>, > Result<Target, Failure> with From<Iterate<Result<Value, Failure>>>
All or the first error: lines.map { text => Int.tryFrom text }.to<Result<List<Int>, NumberParseError>>()
Stops pulling at the first Fail. This is what traverse/sequence is in languages with higher-kinded types.
Errors
The reason of the first Fail of the pipeline. Nothing behind it is pulled, so a step that fails early costs
nothing more.
fn from
static fn from(value: Iterate<Result<Value, Failure>>): Result<Target, Failure>