Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.JsonCreating a Result
Most failures start as something that is not a Result: a predicate over a value, a bool, a TryParse tuple, an
option. These helpers do the conversion and let you attach your own error type.
All examples share one error type:
open System
open Reified.Result
type SignupError =
| NameMissing
| AgeMissing
| AgeNotANumber of string
| AgeOutOfRange of int
SystemReifiedResult03-result-handling_11-creating.md_page.SignupErrorNameMissingAgeMissingAgeNotANumberstringAn abbreviation for the CLI type . Basic Types
AgeOutOfRangeintAn abbreviation for the CLI type . Basic Types
Directly
ok and error construct a Result. They exist so a pipeline can end in one without a type annotation; Ok and
Error work just as well.
Result.ok 42 // Ok 42
Result.error NameMissing // Error NameMissing
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
ok: 'a -> Result<'a,'b>Creates an Ok result.
error: 'a -> Result<'b,'a>Creates an Error result.
NameMissingFrom a predicate over a value
okIf keeps the value when the predicate holds; failIf is its inverse. Both fail with unit, because at that point
there is nothing to say about the failure yet — you attach the reason with orError.
"Ada" |> Result.okIf (String.IsNullOrWhiteSpace >> not)
// Ok "Ada"
"" |> Result.okIf (String.IsNullOrWhiteSpace >> not)
// Error ()
"" |> Result.okIf (String.IsNullOrWhiteSpace >> not) |> Result.orError NameMissing
// Error NameMissing
" " |> Result.failIf String.IsNullOrWhiteSpace |> Result.orError NameMissing
// Error NameMissing
(|>): '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.
okIf: ('input -> bool) -> 'input -> Result<'input,unit>Keeps the input value when the predicate holds, or returns the supplied error. Mirrors Option.filter: predicate first, subject piped last. The error is attached separately with orError so this stays a pure filter, same shape as its Option counterpart.
System.StringRepresents text as a sequence of UTF-16 code units.
IsNullOrWhiteSpace: string -> boolIndicates whether a specified string is , empty, or consists only of white-space characters. The string to test. if the parameter is or , or if consists exclusively of white-space characters.
(>>): ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3Compose two functions, the function on the left being applied first The first function to apply. The second function to apply. The composition of the input functions. let addOne x = x + 1 let doubleIt x = x * 2 let addThenDouble = addOne >> doubleIt addThenDouble 3 // Evaluates to 8
``not``: bool -> boolNegate a logical value. Not True equals False and not False equals True The value to negate. The result of the negation. not (2 + 2 = 5) // Evaluates to true // not is a function that can be compose with other functions let fileDoesNotExist = System.IO.File.Exists >> not
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.
NameMissingfailIf: ('input -> bool) -> 'input -> Result<'input,unit>Keeps the input value when the predicate does not hold, or returns the supplied error. The inverse of okIf: fails when the predicate is true, succeeds otherwise.
Splitting the predicate from the reason keeps the predicate reusable. The same String.IsNullOrWhiteSpace serves
every field, and each call site names its own error.
From a standalone bool
When the condition is already computed and there is no subject value to carry forward, use require. It succeeds
with unit.
true |> Result.require |> Result.orError NameMissing // Ok ()
false |> Result.require |> Result.orError NameMissing // Error NameMissing
(|>): '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.
require: bool -> Result<unit,unit>Requires an already-computed condition where there is no subject value to preserve. The condition is already computed and stands alone, so success produces Ok (). Use okIf/failIf instead when the value under test should flow through. request.AcceptedTerms |> Result.require |> Result.orError TermsNotAccepted
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.
NameMissingThe difference from okIf is behavioural: okIf applies a predicate to a subject and preserves it on success;
require takes an already-computed condition and has no subject to preserve.
From TryParse and Choice
.NET TryParse methods return a bool * 'value tuple. fromTry converts one directly.
Int32.TryParse "36" |> Result.fromTry // Ok 36
Int32.TryParse "abc" |> Result.fromTry // Error ()
Int32.TryParse "abc" |> Result.fromTry |> Result.orError (AgeNotANumber "abc")
// Error (AgeNotANumber "abc")
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, .
(|>): '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.
AgeNotANumberfromChoice converts an F# Choice, which some older APIs return.
Choice1Of2 42 |> Result.fromChoice // Ok 42
Choice2Of2 NameMissing |> Result.fromChoice // Error NameMissing
Choice1Of2Choice 1 of 2 choices
(|>): '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.
fromChoice: Choice<'value,'error> -> Result<'value,'error>Converts an F# Choice into a result.
Choice2Of2Choice 2 of 2 choices
NameMissingFrom an option or value option
fromOption and fromValueOption are the inverse of toOption/toValueOption. They fail with unit, same as
okIf/failIf, so attach the reason with orError.
Some "Ada" |> Result.fromOption |> Result.orError NameMissing // Ok "Ada"
None |> Result.fromOption |> Result.orError NameMissing // Error NameMissing
ValueSome 36 |> Result.fromValueOption |> Result.orError AgeMissing // Ok 36
SomeThe representation of "Value of type 'T" The input value. An option representing the value.
(|>): '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.
fromOption: 'value option -> Result<'value,unit>Converts an option into a unit-error result. The inverse of toOption.
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.
NameMissingNoneThe representation of "No value"
ValueSomeThe representation of "Value of type 'T" The input value. An option representing the value.
fromValueOption: 'value voption -> Result<'value,unit>Converts a value option into a unit-error result. The inverse of toValueOption.
AgeMissingChecking without losing the value: reach for Constraint
A reusable rule that proves a fact about a value belongs in Reified.Constraint, not in Result. Constraint.guard
runs a constraint and hands the original value back on success, so the check can sit in the middle of a pipeline.
open Reified
let positive : Constraint<int> = Constraint.greaterThan 0
36 |> Constraint.guard positive |> Result.mapError (fun _ -> AgeOutOfRange 36) // Ok 36
-1 |> Constraint.guard positive |> Result.mapError (fun _ -> AgeOutOfRange -1) // Error (AgeOutOfRange -1)
Reifiedpositive: Constraint<int>Reified.Constraint`1A reusable description of valid values, coupled to the closures that execute it. One constraint value serves direct checking, refined-value admission, Schema, documentation, and export. There is no separate check type: check is the operation, Constraint is the noun. Both closures are retained deliberately. They are not duplicates of one rule: test over a conjunction may stop at the first failing child, while check must run every child to accumulate. Interpreted atoms and custom predicates therefore have a Boolean path that does no violation work, and combinators preserve that property when every child has it. A customWith constraint supplies only a violation-returning callback, so its test runs that callback and discards the error. The description is never interpreted during execution. Closures are composed once, at construction.
intAn abbreviation for the CLI type . Basic Types
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
greaterThan: 'value -> Constraint<'value>Requires a value strictly greater than the supplied bound. let quantity : Constraint<int> = Constraint.greaterThan 0
(|>): '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
guard: Constraint<'value> -> 'value -> Result<'value,Violation>Runs a constraint and returns the unchanged value after success. let requiredName : Constraint<string> = Constraint.present "Alice" |> Constraint.guard requiredName |> Result.mapError Violation.render
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
mapError: ('a -> 'b) -> Result<'c,'a> -> Result<'c,'b>Maps the error value of a result.
AgeOutOfRangeNote the success type: Constraint.guard returns Ok 36, not Ok (). If the rule is only a local condition, not
something worth naming and reusing, use okIf/failIf with orError instead — see
Constraint vs. Result for the distinction.
Which one to reach for
| Starting point | Function |
|---|---|
| a value and a local predicate | okIf / failIf, then orError |
a bool with no subject value |
require, then orError |
bool * 'value from TryParse |
fromTry |
Choice<'value, 'error> |
fromChoice |
'value option / 'value voption |
fromOption / fromValueOption, then orError |
| a reusable, inspectable rule | Constraint.guard, then orError / mapError |
Next: transforming values once you have a Result.