Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultObserving a Result
tap and tapError run a side effect and hand the result back unchanged. Use them to log without breaking a pipeline apart.
open System
open Reified.Result
type SignupError =
| AgeNotANumber of string
| AgeOutOfRange of int
let parseAge (age: string) : Result<int, SignupError> =
match Int32.TryParse age with
| true, value -> Ok value
| false, _ -> Error(AgeNotANumber age)
SystemReifiedResultFsLiveDocsGeneratedPage15_F2D257DD2F93.SignupErrorAgeNotANumberstringAn abbreviation for the CLI type . Basic Types
AgeOutOfRangeintAn abbreviation for the CLI type . Basic Types
parseAge: string -> Result<int,SignupError>age: stringMicrosoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
System.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: intOkRepresents 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.
Without them
Logging mid-pipeline means naming the intermediate value and returning it again:
let logged =
let outcome = parseAge raw
match outcome with
| Ok age -> printfn "accepted %d" age
| Error failure -> printfn "rejected: %A" failure
outcomeWith them
parseAge "abc"
|> Result.tap (fun age -> printfn "accepted %d" age)
|> Result.tapError (fun failure -> printfn "rejected: %A" failure)
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.
tap: ('value -> unit) -> Result<'value,'error> -> Result<'value,'error>Runs a side effect on the successful value and returns the result unchanged. For logging and diagnostics at a boundary. The effect cannot change the result. Ok 10 |> Result.tap (printfn "loaded %d") // prints, then returns Ok 10
age: intprintfn: Printf.TextWriterFormat<'T> -> 'TPrint to stdout using the given format, and add a newline. The formatter. The formatted result. See Printf.printfn (link: ) for examples.
tapError: ('error -> unit) -> Result<'value,'error> -> Result<'value,'error>Runs a side effect on the error value and returns the result unchanged. Error "boom" |> Result.tapError (printfn "failed: %s") // prints, then returns Error "boom"
failure: SignupErrorrejected: AgeNotANumber "abc"
The value returned is the original Error (AgeNotANumber "abc"). tap did not run, because the result was not Ok;
tapError ran and returned its input untouched.
Both callbacks return unit; neither changes the result:
Result.tap : ('value -> unit) -> Result<'value, 'error> -> Result<'value, 'error>
Result.tapError : ('error -> unit) -> Result<'value, 'error> -> Result<'value, 'error>At a boundary
At a boundary, where you want a record of what happened but the caller still gets the untouched result:
let handleSignup raw =
parseAge raw
|> Result.tapError (fun failure -> logger.Warning("signup rejected: {Failure}", failure))
|> Result.map buildAccountKeep the effect small and total. An effect that throws will propagate out of tap, which defeats the purpose of
working in Result, and because the exception escapes mid-pipeline, the result you were carrying is lost.