Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultThe result computation expression
result { } passes each success to the next step and
returns the first Error:
open System
open Reified.Result
open Reified.ResultDSL
type SignupError =
| NameMissing
| AgeNotANumber of string
| AgeOutOfRange of int
type Signup = { Name: string; Age: int }
let parseName (name: string) : Result<string, SignupError> =
if String.IsNullOrEmpty name then Error NameMissing else Ok name
let parseAge (age: string) : Result<int, SignupError> =
match Int32.TryParse age with
| true, value -> Ok value
| false, _ -> Error(AgeNotANumber age)
SystemReifiedResultReified.ResultDSLThe concise result vocabulary: the result { } computation expression, its accumulating result.list { } / result.array { } variants, and the lightweight admission functions (okIf, failIf, require, orError, mapError). Optional and opt-in, in the same shape as Reified.DataDSL, Reified.ConstraintDSL, and Reified.SchemaDSL: open Reified.Result for Result, then open Reified.ResultDSL for this vocabulary. Deliberately small: generic combinators such as map, bind, orElse, tap, and the traversal helpers stay qualified as Result.map, Result.bind, and so on.
FsLiveDocsGeneratedPage17_9A5DDEA924C8.SignupErrorNameMissingAgeNotANumberstringAn abbreviation for the CLI type . Basic Types
AgeOutOfRangeintAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage17_9A5DDEA924C8.SignupName: stringAge: intparseName: string -> Result<string,SignupError>name: stringMicrosoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
System.StringRepresents text as a sequence of UTF-16 code units.
IsNullOrEmpty: string -> boolIndicates whether the specified string is or an empty string (""). The string to test. if the parameter is or an empty string (""); otherwise, .
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
parseAge: string -> Result<int,SignupError>age: 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, .
value: intRequires & types
Requires: open System (packages: Reified.Result)val parseName: string -> Result<string,SignupError>val parseAge: string -> Result<int,SignupError>What the keywords do
let! binds the value inside Ok to the name on its left. do! runs a step whose success value is unit, so there
is no name. return wraps a plain value back up as Ok. return! uses a complete Result as the block's result.
result {
let! (name: string) = (parseName "Ada": Result<string, SignupError>)
let! (age: int) = (parseAge "36": Result<int, SignupError>)
return { Name = name; Age = age }
}
// Result<Signup, SignupError>
result: ResultBuilderThe fail-fast result { } computation expression.
name: stringstringAn abbreviation for the CLI type . Basic Types
parseName: string -> Result<string,SignupError>Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
FsLiveDocsGeneratedPage17_9A5DDEA924C8.SignupErrorage: intintAn abbreviation for the CLI type . Basic Types
parseAge: string -> Result<int,SignupError>Name: stringAge: intRequires & types
Requires: open Reified.ResultDSL (packages: Reified.Result)On the right of let! is a Result<'value, 'error>; on the left is the 'value. The block's own type is
Result<'whatever you return, 'error>, the error type is shared by every step, which is why they all fail with
SignupError here.
do! and return! in the same block:
let requireAdult age : Result<unit, SignupError> =
if age >= 18 then Ok () else Error (AgeOutOfRange age)
let signupAdultOnly name age =
result {
let! name = parseName name
let! age = parseAge age
do! requireAdult age // unit success, nothing to bind
return! Ok { Name = name; Age = age }
}
signupAdultOnly "Ada" "36" // Ok { Name = "Ada"; Age = 36 }
signupAdultOnly "Ada" "12" // Error (AgeOutOfRange 12)
requireAdult: int -> Result<unit,SignupError>age: intMicrosoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
FsLiveDocsGeneratedPage17_9A5DDEA924C8.SignupError(>=): '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
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.
AgeOutOfRangesignupAdultOnly: string -> string -> Result<Signup,SignupError>name: stringage: stringresult: ResultBuilderThe fail-fast result { } computation expression.
parseName: string -> Result<string,SignupError>parseAge: string -> Result<int,SignupError>Name: stringAge: intRequires & types
Requires: open Reified.ResultDSL (packages: Reified.Result)val requireAdult: int -> Result<unit,SignupError>val signupAdultOnly: string -> string -> Result<Signup,SignupError>do! runs a step whose success value is unit, so there is no name to bind; return! uses a complete Result as the
block's result.
Failure stops the block
let signup name age =
result {
let! name = parseName name
let! age = parseAge age
return { Name = name; Age = age }
}
signup "Ada" "36" // Ok { Name = "Ada"; Age = 36 }
signup "" "36" // Error NameMissing
signup "Ada" "abc" // Error (AgeNotANumber "abc")
signup: string -> string -> Result<Signup,SignupError>name: stringage: stringresult: ResultBuilderThe fail-fast result { } computation expression.
parseName: string -> Result<string,SignupError>age: intparseAge: string -> Result<int,SignupError>Name: stringAge: intRequires & types
Requires: open Reified.ResultDSL (packages: Reified.Result)val signup: string -> string -> Result<Signup,SignupError>signup "" "36" returns Error NameMissing; the age is never parsed because the block stops at the first failure, so
its own failure is never seen. A later binding does not run after an earlier one fails.
Control flow inside a block
The builder supports the ordinary constructs, so a block is not restricted to a straight run of bindings. The
reader and validation calls below are placeholders for the caller's own host I/O; result { } only sequences them
and never opens a file or performs I/O itself:
result {
use reader = openReader path // disposed on the way out, success or failure
let! header = readHeader reader
for line in lines do // stops at the first failing iteration
do! validate line
while not (isDone ()) do
do! step ()
return header
}try/with and try/finally work as usual. Note what they do and do not catch: they handle .NET exceptions, not
Error values. An Error is an ordinary return value, so it does not trigger with, it just ends the block.
When to use it
- a later step needs a value bound by an earlier one;
- continuing after a failure makes no sense;
- the pipeline has grown past two or three steps and the nesting from
bindis getting hard to read.
When the steps are independent of each other and the caller should hear about all the failures rather than the first, use collecting every error instead.