Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.JsonCompose Parse and Refinement
Parsing and refinement have different failure types because they answer different questions. Define an application
error that preserves that distinction, then compose with result { }.
open Reified
open Reified.Refinements
open Reified.Result
open Reified.ResultDSL
type QuantityError =
| InvalidInteger of ParseError
| InvalidQuantity of Violation
let quantity raw =
result {
let! parsed = Parse.int raw |> Result.mapError InvalidInteger
let! quantity = parsed |> Constraint.guard (Constraint.greaterThan 0) |> Result.mapError InvalidQuantity
return quantity
}
ReifiedRefinementsResultReified.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.
06-refined_30-composition.md_page.QuantityErrorInvalidIntegerReified.ParseErrorPrimitive parse failures returned by Parse helpers.
InvalidQuantityReified.ViolationWhy a value failed its constraint. A diagnostic contract, not an application error union. Domain code maps a whole violation once with Result.mapError; Schema adds the path at which it occurred. Violations are plain comparable data. No closure and no constraint description is reachable from one, so structural equality holds and a violation can be retained and compared long after the constraint that produced it went out of scope. There is no promised wire format. Reified-produced groups are never empty and never unary: a single failing child is returned directly rather than wrapped. The first * rest shape encodes non-emptiness only; non-unarity is a normalization invariant.
quantity: string -> Result<int,QuantityError>raw: stringresult: ResultBuilderThe fail-fast result { } computation expression.
parsed: intReified.ParseModulePrimitive parsers for untrusted serialized input.
int: string -> Result<int,ParseError>Parses a 32-bit integer.
(|>): '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.
mapError: ('a -> 'b) -> Result<'c,'a> -> Result<'c,'b>Maps the error value of a result.
quantity: intReified.ConstraintModuleCreates, executes, composes, and inspects constraints.
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
greaterThan: 'value -> Constraint<'value>Requires a value strictly greater than the supplied bound. let quantity : Constraint<int> = Constraint.greaterThan 0
Compose several values
type OrderInputError =
| InvalidQuantityText of ParseError
| InvalidQuantity of Violation
| InvalidSku of Violation
let orderLine rawQuantity rawSku =
result {
let! parsed = Parse.int rawQuantity |> Result.mapError InvalidQuantityText
let! quantity = parsed |> Constraint.guard (Constraint.greaterThan 0) |> Result.mapError InvalidQuantity
let! sku = Refine.nonBlankString rawSku |> Result.mapError InvalidSku
return quantity, sku
}
06-refined_30-composition.md_page.OrderInputErrorInvalidQuantityTextReified.ParseErrorPrimitive parse failures returned by Parse helpers.
InvalidQuantityReified.ViolationWhy a value failed its constraint. A diagnostic contract, not an application error union. Domain code maps a whole violation once with Result.mapError; Schema adds the path at which it occurred. Violations are plain comparable data. No closure and no constraint description is reachable from one, so structural equality holds and a violation can be retained and compared long after the constraint that produced it went out of scope. There is no promised wire format. Reified-produced groups are never empty and never unary: a single failing child is returned directly rather than wrapped. The first * rest shape encodes non-emptiness only; non-unarity is a normalization invariant.
InvalidSkuorderLine: string -> string -> Result<(int * NonBlankString),OrderInputError>rawQuantity: stringrawSku: stringresult: ResultBuilderThe fail-fast result { } computation expression.
parsed: intReified.ParseModulePrimitive parsers for untrusted serialized input.
int: string -> Result<int,ParseError>Parses a 32-bit integer.
(|>): '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.
mapError: ('a -> 'b) -> Result<'c,'a> -> Result<'c,'b>Maps the error value of a result.
quantity: intReified.ConstraintModuleCreates, executes, composes, and inspects constraints.
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
greaterThan: 'value -> Constraint<'value>Requires a value strictly greater than the supplied bound. let quantity : Constraint<int> = Constraint.greaterThan 0
sku: NonBlankStringReified.Refinements.RefineModuleSmart constructors for built-in refined values, and the refinements behind them. Text, Character, Collection, and Choice are nested here rather than declared beside Refine. Each names a concept general enough to shadow something a caller already has in scope, and none of them is a companion to a type this package exports, so there is nothing to gain from the type-and-module pairing that keeps NonBlankString and DistinctList at the top level.
nonBlankString: string -> Result<NonBlankString,Violation>Every bind names the operation and the error translation. The application decides whether two failures share a case or remain distinct.
Reuse a refinement
Application-defined types expose a named refinement value:
let customerId raw =
result {
let! parsed = Parse.int raw |> Result.mapError InvalidCustomerIdText
let! id = Refinement.create CustomerId.refinement parsed |> Result.mapError InvalidCustomerId
return id
}Use ordinary functions when additional configuration is required. Parse.optional, Refine.Choice.orElse, and
Refinement.create all compose through standard Result functions.
Continue with Define Refined Types.