Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.JsonWorking with collections
Applying a fallible operation to every item in a list leaves you with a list of results, which is rarely what you
want. traverse and sequence turn that inside out: one result holding every value.
open System
open Reified.Result
type SignupError =
| AgeMissing
| AgeNotANumber of string
| AgeOutOfRange of int
let parseAge (raw: string) =
Int32.TryParse raw
|> Result.fromTry
|> Result.orError (AgeNotANumber raw)
|> Result.bind (fun age ->
if age >= 0 && age < 130 then Ok age else Error (AgeOutOfRange age))
SystemReifiedResult03-result-handling_50-collections.md_page.SignupErrorAgeMissingAgeNotANumberstringAn abbreviation for the CLI type . Basic Types
AgeOutOfRangeintAn abbreviation for the CLI type . Basic Types
parseAge: string -> Result<int,SignupError>raw: stringSystem.Int32Represents a 32-bit signed integer.
TryParse: string * byref<int> -> boolConverts the string representation of a number to its 32-bit signed integer equivalent. A return value indicates whether the conversion succeeded. A string containing a number to convert. When this method returns, contains the 32-bit signed integer value equivalent of the number contained in , if the conversion succeeded, or zero if the conversion failed. The conversion fails if the parameter is or , is not of the correct format, or represents a number less than Int32.MinValue or greater than Int32.MaxValue. This parameter is passed uninitialized; any value originally supplied in will be overwritten. if was converted successfully; otherwise, .
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
fromTry: bool * 'value -> Result<'value,unit>Converts a .NET Try* tuple into a unit-error result.
orError: 'error -> Result<'value,'discardedError> -> Result<'value,'error>Replaces whatever error a result carries with the supplied typed error. Ok passes through unchanged. The natural follow-up to okIf/failIf, which fail with unit precisely so the reason is chosen here: value |> Result.okIf isValid |> Result.orError MyError. Use Result.mapError instead when the existing error carries something worth keeping, as a Violation does.
bind: ('a -> Result<'b,'c>) -> Result<'a,'c> -> Result<'b,'c>Binds a result to the next fail-fast operation.
age: int(>=): 'T -> 'T -> boolStructural greater-than-or-equal The first parameter. The second parameter. The result of the comparison. 5 >= 1 // Evaluates to true 5 >= 5 // Evaluates to true [1; 5] >= [1; 6] // Evaluates to false
(&&): bool -> bool -> boolBinary 'and'. When used as a binary operator the right hand value is evaluated only on demand The first value. The second value. The result of the operation.
(<): 'T -> 'T -> boolStructural less-than comparison The first parameter. The second parameter. The result of the comparison. 1 < 5 // Evaluates to true 5 < 5 // Evaluates to false (1, "a") < (1, "z") // Evaluates to true
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
traverse
traverse maps each item with a Result-returning function and collects the successes.
[ "1"; "2"; "3" ] |> Result.traverse parseAge
// Ok [1; 2; 3]
[ "1"; "abc"; "500" ] |> Result.traverse parseAge
// Error (AgeNotANumber "abc")
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
traverse: ('input -> Result<'output,'error>) -> 'input seq -> Result<'output list,'error>Maps each value with a result-returning function, stopping at the first error. Takes any sequence and always produces a list. Traversal stops at the first error, so later mappings do not run. Use one of the accumulating builders when every error should be reported. [ "1"; "2" ] |> Result.traverse parseInt // Ok [ 1; 2 ]
parseAge: string -> Result<int,SignupError>sequence
sequence is the same operation when you already hold the results — it is traverse id.
[ Ok 1; Ok 2 ] |> Result.sequence
// Ok [1; 2]
[ Ok 1; Error AgeMissing; Ok 3 ] |> Result.sequence
// Error AgeMissing
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
sequence: Result<'value,'error> seq -> Result<'value list,'error>Turns a sequence of results into one fail-fast result containing all successes. Takes any sequence and always produces a list. Stops at the first error. [ Ok 1; Error "missing"; Ok 3 ] |> Result.sequence // Error "missing"
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
AgeMissingIt stops at the first error
Traversal is fail-fast, and this is observable: the mapping does not run for items after the failure. This matters when the mapping does real work — a lookup, a request, a write.
let mutable visited = []
let recordAndParse raw =
visited <- raw :: visited
parseAge raw
[ "1"; "abc"; "3" ] |> Result.traverse recordAndParse
// Error (AgeNotANumber "abc")
List.rev visited
// ["1"; "abc"]
visited: string listrecordAndParse: string -> Result<int,SignupError>raw: string(::)parseAge: string -> Result<int,SignupError>(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
traverse: ('input -> Result<'output,'error>) -> 'input seq -> Result<'output list,'error>Maps each value with a result-returning function, stopping at the first error. Takes any sequence and always produces a list. Traversal stops at the first error, so later mappings do not run. Use one of the accumulating builders when every error should be reported. [ "1"; "2" ] |> Result.traverse parseInt // Ok [ 1; 2 ]
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
rev: 'T list -> 'T listReturns a new list with the elements in reverse order. The input list. The reversed list. let inputs = [ 0; 1; 2 ] inputs |> List.rev Evaluates to [ 2; 1; 0 ].
"3" was never visited. Only the first failure is reported, and later items are not examined at all — so this cannot
tell a user everything wrong with their input.
traverseAll and sequenceAll
Use these when every item should be tried and every failure reported:
[ "1"; "abc"; "500" ] |> Result.traverseAll parseAge
// Error [AgeNotANumber "abc"; AgeOutOfRange 500]
[ Ok 1; Error AgeMissing; Error (AgeNotANumber "x") ] |> Result.sequenceAll
// Error [AgeMissing; AgeNotANumber "x"]
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
traverseAll: ('input -> Result<'output,'error>) -> 'input seq -> Result<'output list,'error list>Maps each value with a result-returning function, running every mapping and collecting every error. Takes any sequence and always produces a list. The sequence is enumerated once, in order, and every mapping runs even after one fails, so a mapping with side effects runs for every item. Errors appear in input order. Each mapping contributes one error; nothing is flattened. Use traverse when the first failure should stop the work. [ "1"; "x"; "y" ] |> Result.traverseAll parseInt // Error [ NotANumber "x"; NotANumber "y" ]
parseAge: string -> Result<int,SignupError>OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
AgeMissingAgeNotANumbersequenceAll: Result<'value,'error> seq -> Result<'value list,'error list>Turns a sequence of results into one result containing all successes, or every error. Takes any sequence and always produces a list. Errors appear in input order. [ Ok 1; Error "missing"; Error "invalid" ] |> Result.sequenceAll // Error [ "missing"; "invalid" ]
The rules:
- Every mapping runs, in input order, even after one has failed. If the mapping does real work, it does that work for every item.
- Errors come back in input order, one per failing item. Nothing is flattened: a mapping that itself returns
Result<_, 'error list>produces a list of lists, so map it down first if you want one flat list. - The input sequence is enumerated once, up front, so a lazy sequence is fully forced and an infinite one will not terminate.
To accumulate across differently-typed independent steps rather than across one collection, see collecting every error.
Shape in and shape out
Both take any seq<_> and produce a list:
Result.traverse : ('a -> Result<'b, 'e>) -> seq<'a> -> Result<'b list, 'e>
Result.sequence : seq<Result<'a, 'e>> -> Result<'a list, 'e>
Result.traverseAll : ('a -> Result<'b, 'e>) -> seq<'a> -> Result<'b list, 'e list>
Result.sequenceAll : seq<Result<'a, 'e>> -> Result<'a list, 'e list>An array or a seq goes in; a list comes out. Convert afterwards with Result.map when you need a different shape:
[| "1"; "2" |] |> Result.traverse parseAge |> Result.map Array.ofList
// Ok [|1; 2|]
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
traverse: ('input -> Result<'output,'error>) -> 'input seq -> Result<'output list,'error>Maps each value with a result-returning function, stopping at the first error. Takes any sequence and always produces a list. Traversal stops at the first error, so later mappings do not run. Use one of the accumulating builders when every error should be reported. [ "1"; "2" ] |> Result.traverse parseInt // Ok [ 1; 2 ]
parseAge: string -> Result<int,SignupError>map: ('a -> 'b) -> Result<'a,'c> -> Result<'b,'c>Maps the success value of a result.
Microsoft.FSharp.Collections.ArrayModuleContains operations for working with arrays. See also F# Language Guide - Arrays.
ofList: 'T list -> 'T arrayBuilds an array from the given list. The input list. The array of elements from the list. let inputs = [ 1; 2; 5 ] inputs |> Array.ofList Evaluates to [| 1; 2; 5 |].
traverse and sequence enumerate the input up to the failure point; traverseAll and sequenceAll enumerate all
of it. An infinite sequence will not terminate through any of them.