7. Errors with Result
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
An exception jumps out of the normal control flow. Result<'ok,'error> keeps an expected failure in the function’s return type:
type Result<'ok, 'error> = | Ok of 'ok | Error of 'errorOfficial references: the Result module and F# error handling.
Validate at the boundary
Section titled “Validate at the boundary”From l07_result.fsx:
type Fret = private Fret of int
module Fret = let create value = if value >= 0 && value <= 24 then Ok (Fret value) else Error $"fret {value} is outside 0..24"The private union case means callers cannot construct Fret -1; they must use Fret.create. The error is expected input data, not an exceptional system failure.
Parse, then validate
Section titled “Parse, then validate”let parseInt (text: string) = match System.Int32.TryParse text with | true, value -> Ok value | false, _ -> Error $"'{text}' is not an integer"
let parseFret text = text |> parseInt |> Result.bind Fret.createResult.bind calls the next function only for Ok; an Error passes through unchanged. This is railway-oriented programming without a special framework.
The string annotation is significant on .NET 10: Int32.TryParse has string, character-span and UTF-8 byte-span overloads. Without the annotation, F# reports FS0041 because it cannot choose an overload before seeing a caller.
Measured cases:
OK 3 -> 3ERROR -1 -> fret -1 is outside 0..24ERROR x -> 'x' is not an integersum: Ok 10Map, bind and compose
Section titled “Map, bind and compose”Result.map ftransforms the successful value with a function that cannot fail.Result.bind fcontinues with a function returning anotherResult.Result.mapError fchanges the error representation.- pattern matching handles the final branch where the application must decide what to display or return.
Use exceptions for broken invariants, cancellation and infrastructure boundaries that already throw. Catch narrowly, translate once into a domain error when recovery is meaningful, and retain the original diagnostic for logs.
Exercise
Section titled “Exercise”Parse a six-element fingering such as x 3 2 0 1 0. Return all validation errors rather than stopping at the first one. This requires an applicative validation shape, not ordinary Result.bind; compare the trade-off before implementing it.
Next, computation expressions remove the repetitive bind plumbing from a parser while preserving the same error path.