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)
SystemReifiedResultFsLiveDocsGeneratedPage16_9A5DDEA924C8.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.
Requires & types
Requires: open Systemval parseAge: string -> Result<int,SignupError>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: SignupErrorRequires & types
Requires: open Reified.Result (packages: Reified.Result)rejected: 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 buildAccountfailure can be AgeNotANumber raw, which keeps the caller's text verbatim, so logger.Warning(..., failure) writes
that raw input out as-is. "Total" here means the callback never throws, not that its payload is safe to persist: when
a field may hold secrets, credentials, or personal data, redact the failure before logging it.
Keep 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.