Result type
A.k.a Either
(Haskell)
Models a double-track Success/Failure
type Result<'Success, 'Error> = // 2 generic parameters
| Ok of 'Success // Success Track
| Error of 'Error // Failure Track
Functional way of dealing with business errors (expected errors)
Allows exceptions to be used only for exceptional errors
As soon as an operation fails, the remaining operations are not launched
🔗 Railway-oriented programming (ROP)
Result module
Main functions:
map f result
: to map the success
• ('T -> 'U) -> Result<'T, 'Error> -> Result<'U, 'Error>
mapError f result
: to map the error
• ('Err1 -> 'Err2) -> Result<'T, 'Err1> -> Result<'T, 'Err2>
bind f result
: same as map
with f
returning a Result
• ('T -> Result<'U, 'Error>) -> Result<'T, 'Error> -> Result<'U, 'Error>
• 💡 The result is flattened, like the flatMap
function on JS arrays
• ⚠️ Same type of 'Error
for f
and the input result
.
💡 Since F♯ 7.0, more functions have been added to the Result
module, but some functions are missing compared to the Option
module:
Ok value
Some value
[ value ]
Error err
None
[ ]
Result.isOk
/ Result.isError
Option.isSome
/ Option.isNone
× / List.isEmpty
Result.contains okValue
Option.contains someValue
List.contains value
Result.count
Option.count
List.length
❗ (O(N))
Result.defaultValue value
Option.defaultValue value
×
Result.defaultWith defThunk
Option.defaultWith defThunk
×
×
Option.filter predicate
List.filter predicate
Result.iter action
Option.iter action
List.iter action
Result.map f
Option.map f
List.map f
Result.mapError f
×
×
Result.bind f
Option.bind f
List.bind f
Result.fold f state
Option.fold f state
List.fold f state
Result.toArray
/ toList
Option.toArray
/ toList
×
Result.toOption
×
×
Quiz 🎲
Implement Result.map
and Result.bind
💡 Tips:
Map the Success track
Access the Success value using pattern matching
Success/Failure tracks
map
: no track change
Track Input Operation Output
Success ─ Ok x ───► map( x -> y ) ───► Ok y
Failure ─ Error e ───► map( .... ) ───► Error e
bind
: eventual routing to Failure track, but never vice versa
Track Input Operation Output
Success ─ Ok x ─┬─► bind( x -> Ok y ) ───► Ok y
└─► bind( x -> Error e2 ) ─┐
Failure ─ Error e ───► bind( .... ) ─┴─► Error ~
☝ The mapping/binding operation is never executed in track Failure.
Result
vs Option
Result
vs Option
Option
can represent the result of an operation that may fail
☝ But if it fails, the option doesn't contain the error, just None
Option<'T>
≃ Result<'T, unit>
→ Some x
≃ Ok x
→ None
≃ Error ()
→ See Result.toOption
(built-in) and Result.ofOption
(below)
[<RequireQualifiedAccess>]
module Result =
let ofOption error option =
match option with
| Some x -> Ok x
| None -> Error error
📅 Dates
The
Option
type is part of F# from the get goThe
Result
type is more recent: introduced in F# 4.1 (2016)
📝 Memory
The
Option
type (alias:option
) is a regular union: a reference typeThe
Result
type is a struct union: a value typeThe
ValueOption
type (alias:voption
) is a struct union:ValueNone | ValueSome of 't
Example
Let's change our previous checkAnswer
to indicate the Error
:
type Answer = A | B | C | D
type Error = InvalidInput of string | WrongAnswer of Answer
let tryParseAnswer =
function
| "A" -> Ok A
| "B" -> Ok B
| "C" -> Ok C
| "D" -> Ok D
| s -> Error(InvalidInput s)
let checkAnswerIs expected actual =
if actual = expected then Ok actual else Error(WrongAnswer actual)
let printAnswerCheck (givenAnswer: string) =
tryParseAnswer givenAnswer
|> Result.bind (checkAnswerIs B)
|> function
| Ok x -> printfn $"%A{x}: ✅ Correct"
| Error(WrongAnswer x) -> printfn $"%A{x}: ❌ Wrong Answer"
| Error(InvalidInput s) -> printfn $"%s{s}: ❌ Invalid Input"
printAnswerCheck "X";; // X: ❌ Invalid Input
printAnswerCheck "A";; // A: ❌ Wrong Answer
printAnswerCheck "B";; // B: ✅ Correct
Last updated
Was this helpful?