Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultConstraintDSL
ConstraintDSL exposes common constructors without the Constraint. prefix:
open Reified
module SignupRules =
open Reified.ConstraintDSL
let name : Constraint<string> =
Constraint.all [ present; minLength 2; maxLength 80 ]
let emailAddress : Constraint<string> =
Constraint.all [ present; email; maxLength 254 ]
let age : Constraint<int> =
atLeast 13
let handle : Constraint<string> =
Constraint.all [ present; alphanumeric; noneOf [ "admin"; "root" ] ]
ReifiedFsLiveDocsGeneratedPage2_163BCE492C58.SignupRulesReified.ConstraintDSLConstraint constructors usable without the Constraint. prefix inside a module that declares value rules. Optional vocabulary, not another abstraction. Opening it makes a declaration read minLength 3 instead of Constraint.minLength 3; everything here is the same value the qualified name returns. Some constructors are deliberately left out because they shadow names the same validation code is likely to need in scope: contains, distinct, all, any, length, and between shadow core F# operations. Reach for those as Constraint.contains, Constraint.all, and so on, even inside a module that has opened this DSL. Constraint execution (Constraint.satisfies, Constraint.check, Constraint.guard) is likewise always qualified: ConstraintDSL declares constraints, Constraint.* executes and inspects them. orError and mapError are structural adapters matching the corresponding Result operations. They let a constraint pipeline retain its input and finish with the application's error type without adding an Reified.Result dependency. module SignupRules = open Reified.ConstraintDSL let age : Constraint<int> = atLeast 13 let contact : Constraint<string> = Constraint.all [ present; email ] let requireContact value = value |> Constraint.guard contact |> orError EmailRequired
name: Constraint<string>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.
stringAn abbreviation for the CLI type . Basic Types
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
all: Constraint<'value> list -> Constraint<'value>Requires every constraint to hold, evaluating each in declaration order and accumulating failures. The empty list is the satisfied identity. F# visits list elements left to right, so annotate the binding when the first element is a type-directed value: let requiredName : Constraint<string> = Constraint.all [ Constraint.present; Constraint.lengthBetween 2 40 ]. let requiredName : Constraint<string> = Constraint.all [ Constraint.present; Constraint.lengthBetween 2 40 ]
present: Constraint<^value>Alias for .
minLength: int -> Constraint<^value>Alias for .
maxLength: int -> Constraint<^value>Alias for .
emailAddress: Constraint<string>email: Constraint<string>Alias for .
age: Constraint<int>intAn abbreviation for the CLI type . Basic Types
atLeast: 'a -> Constraint<'a>Alias for .
handle: Constraint<string>alphanumeric: Constraint<string>Alias for .
noneOf: 'a seq -> Constraint<'a>Alias for .
Requires & types
Requires: open Reified; open Reified.ConstraintDSL (packages: Reified.Constraint)val name: Constraint<string>val emailAddress: Constraint<string>val age: Constraint<int>val handle: Constraint<string>Each DSL name returns the same Constraint<'value> as its qualified counterpart.
Type-directed names
present, blank, optional, and the size family pick their shape from the type they are used at, so most of the
time they just work:
open Reified
open Reified.SchemaDSL
open Reified.ConstraintDSL
type Item = { Sku: string }
let name : Constraint<string> = Constraint.all [ present; maxLength 80 ]
let tags : Constraint<Item list> = Constraint.all [ atLeastOne; Constraint.distinct ]
Schema.text |> Schema.constrain present
ReifiedReified.SchemaDSLThe concise schema-definition vocabulary: the record computation expression, its field and constructor forms, and the collection-schema operations. Optional and opt-in, in the same shape as Reified.DataDSL and Reified.ConstraintDSL: open Reified.Schema for Schema, then open Reified.SchemaDSL for this vocabulary. There is no constraint catalogue here. One Constraint vocabulary serves direct checking, refinement, and Schema, so a field block reaches for Constraint.email or an opened Reified.ConstraintDSL exactly as standalone code does. Boundary supply is Schema-owned and stays here as mustSupply and mayOmit.
Reified.ConstraintDSLConstraint constructors usable without the Constraint. prefix inside a module that declares value rules. Optional vocabulary, not another abstraction. Opening it makes a declaration read minLength 3 instead of Constraint.minLength 3; everything here is the same value the qualified name returns. Some constructors are deliberately left out because they shadow names the same validation code is likely to need in scope: contains, distinct, all, any, length, and between shadow core F# operations. Reach for those as Constraint.contains, Constraint.all, and so on, even inside a module that has opened this DSL. Constraint execution (Constraint.satisfies, Constraint.check, Constraint.guard) is likewise always qualified: ConstraintDSL declares constraints, Constraint.* executes and inspects them. orError and mapError are structural adapters matching the corresponding Result operations. They let a constraint pipeline retain its input and finish with the application's error type without adding an Reified.Result dependency. module SignupRules = open Reified.ConstraintDSL let age : Constraint<int> = atLeast 13 let contact : Constraint<string> = Constraint.all [ present; email ] let requireContact value = value |> Constraint.guard contact |> orError EmailRequired
FsLiveDocsGeneratedPage3_163BCE492C58.ItemSku: stringstringAn abbreviation for the CLI type . Basic Types
name: Constraint<string>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.
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
all: Constraint<'value> list -> Constraint<'value>Requires every constraint to hold, evaluating each in declaration order and accumulating failures. The empty list is the satisfied identity. F# visits list elements left to right, so annotate the binding when the first element is a type-directed value: let requiredName : Constraint<string> = Constraint.all [ Constraint.present; Constraint.lengthBetween 2 40 ]. let requiredName : Constraint<string> = Constraint.all [ Constraint.present; Constraint.lengthBetween 2 40 ]
present: Constraint<^value>Alias for .
maxLength: int -> Constraint<^value>Alias for .
tags: Constraint<Item list>listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
atLeastOne: Constraint<^value>Alias for .
distinct: Constraint<^container>Requires a collection to hold no duplicates. The first repeat is reported as the actual value. let tags : Constraint<string list> = Constraint.distinct
Reified.SchemaConstruction, composition, parsing, and checking for universal schemas.
text: Schema<string>Describes text input.
(|>): '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
constrain: Constraint<'a> -> Schema<'a> -> Schema<'a>Requires a schema's values to satisfy a constraint. The same Constraint value serves direct checking, refinement, and Schema. For a value schema the constraint runs at that layer; for a model schema it runs after successful field admission and construction. Schema.text |> Schema.constrain (Constraint.lengthBetween 2 40)
Here the surrounding type selects the shape. A standalone binding needs a type annotation because dispatch runs on the return type:
open Reified
open Reified.ConstraintDSL
type Item = { Sku: string }
let requiredName : Constraint<string> = present
let selectedPlan : Constraint<string option> = present
let requiredItems : Constraint<Item list> = minLength 1
ReifiedReified.ConstraintDSLConstraint constructors usable without the Constraint. prefix inside a module that declares value rules. Optional vocabulary, not another abstraction. Opening it makes a declaration read minLength 3 instead of Constraint.minLength 3; everything here is the same value the qualified name returns. Some constructors are deliberately left out because they shadow names the same validation code is likely to need in scope: contains, distinct, all, any, length, and between shadow core F# operations. Reach for those as Constraint.contains, Constraint.all, and so on, even inside a module that has opened this DSL. Constraint execution (Constraint.satisfies, Constraint.check, Constraint.guard) is likewise always qualified: ConstraintDSL declares constraints, Constraint.* executes and inspects them. orError and mapError are structural adapters matching the corresponding Result operations. They let a constraint pipeline retain its input and finish with the application's error type without adding an Reified.Result dependency. module SignupRules = open Reified.ConstraintDSL let age : Constraint<int> = atLeast 13 let contact : Constraint<string> = Constraint.all [ present; email ] let requireContact value = value |> Constraint.guard contact |> orError EmailRequired
FsLiveDocsGeneratedPage4_163BCE492C58.ItemSku: stringstringAn abbreviation for the CLI type . Basic Types
requiredName: Constraint<string>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.
present: Constraint<^value>Alias for .
selectedPlan: Constraint<string option>optionThe type of optional values. When used from other CLI languages the empty option is the null value. Use the constructors Some and None to create values of this type. Use the values in the Option module to manipulate values of this type, or pattern match against the values directly. 'None' values will appear as the value null to other CLI languages. Instance methods on this type will appear as static methods to other CLI languages due to the use of null as a value representation. Options
requiredItems: Constraint<Item list>listThe type of immutable singly-linked lists. See the module for further operations related to lists. Use the constructors [] and :: (infix) to create values of this type, or the notation [1; 2; 3]. Use the values in the List module to manipulate values of this type, or pattern match against the values directly. See also F# Language Guide - Lists.
minLength: int -> Constraint<^value>Alias for .
This selects a constraint; it does not parse, convert, or refine anything.
The catalogue
| Family | Names |
|---|---|
| Presence | present, blank, optional |
| Size | minLength, maxLength, lengthBetween, single, atLeastOne, atMostOne, moreThanOne |
| Comparison | equalTo, notEqualTo, greaterThan, lessThan, atLeast, atMost |
| Sign | positive, nonNegative, negative, nonPositive |
| Membership | oneOf, noneOf, notContains |
| Format | email, trimmed, numeric, alphanumeric, pattern |
| Number | multipleOf, finite, finite32 |
| Opaque | notWith, custom, customLocalized, customWith, contramap |
| Other | describe, orError, mapError |
The sign and size names are spellings, not new primitives: positive is greaterThan 0 at the value's own numeric
type, and atLeastOne is minLength 1. Each builds the same atom its general form builds, so inspection, export,
and generation treat them identically.
Text sizes count code points
The size family measures text in Unicode code points, not UTF-16 code units. An emoji outside the Basic
Multilingual Plane is one character, so Constraint.length 1 accepts "\U0001F600", where String.Length
would report 2.
let emoji : Constraint<string> = Constraint.length 1
Constraint.satisfies emoji "\U0001F600" // true
emoji: Constraint<string>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.
stringAn abbreviation for the CLI type . Basic Types
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
length: int -> Constraint<^value>Requires text or a collection to have exactly the supplied size. Text sizes count Unicode code points, so one emoji counts once even though it is two UTF-16 units. let code : Constraint<string> = Constraint.length 6
satisfies: Constraint<'value> -> 'value -> boolAnswers whether a value satisfies a constraint, without building a violation. let retryCount = Constraint.between 0 10 3 |> Constraint.satisfies retryCount // true
Requires & types
Requires: open Reified (packages: Reified.Constraint)val emoji: Constraint<string>Counting code points gives the same size in a browser and on a server. Collections count elements, and maps count
entries; the atom is shape-neutral and an interpreter combines it with the surrounding shape to reach maxLength,
maxItems, or maxProperties.
Blankness is defined the same way on both runtimes: .NET's whitespace set plus U+FEFF. Adding U+FEFF makes a JSON
Schema validator's whitespace a strict subset of Reified's, which is what lets present and trimmed export a
sound \S pattern instead of staying runtime-only.
Names left off, and why
Some constructors are deliberately absent because they shadow names the same validation code is likely to need:
| Left off | Reason | Reach for |
|---|---|---|
all, any, contains, distinct, length, between |
shadow core F# operations | Constraint.all, Constraint.contains, … |
check |
shadows Schema.check |
Constraint.check |
test, guard |
execution stays qualified, consistent with check |
Constraint.satisfies, Constraint.guard |
The omissions are driven by collision alone, which is why notContains is exported although contains is not, no
core operation is named notContains. All constraint execution, Constraint.satisfies, Constraint.check,
Constraint.guard, and Constraint.inspect, stays qualified, so ConstraintDSL declares constraints and
Constraint.* executes and inspects them.
Result adapters
orError and mapError are small structural adapters matching the corresponding Result operations. They live here
because Reified.Constraint does not depend on Reified.Result, so a constraint pipeline can retain its input and
finish with the application's own error type without adding a package reference:
open Reified
open Reified.ConstraintDSL
type SignupError =
| NameRequired
| InvalidQuantity of Violation
let requiredName (value: string) =
value |> Constraint.guard present |> orError NameRequired
let quantity value =
value |> Constraint.guard (atLeast 1) |> mapError InvalidQuantity
ReifiedReified.ConstraintDSLConstraint constructors usable without the Constraint. prefix inside a module that declares value rules. Optional vocabulary, not another abstraction. Opening it makes a declaration read minLength 3 instead of Constraint.minLength 3; everything here is the same value the qualified name returns. Some constructors are deliberately left out because they shadow names the same validation code is likely to need in scope: contains, distinct, all, any, length, and between shadow core F# operations. Reach for those as Constraint.contains, Constraint.all, and so on, even inside a module that has opened this DSL. Constraint execution (Constraint.satisfies, Constraint.check, Constraint.guard) is likewise always qualified: ConstraintDSL declares constraints, Constraint.* executes and inspects them. orError and mapError are structural adapters matching the corresponding Result operations. They let a constraint pipeline retain its input and finish with the application's error type without adding an Reified.Result dependency. module SignupRules = open Reified.ConstraintDSL let age : Constraint<int> = atLeast 13 let contact : Constraint<string> = Constraint.all [ present; email ] let requireContact value = value |> Constraint.guard contact |> orError EmailRequired
FsLiveDocsGeneratedPage5_163BCE492C58.SignupErrorNameRequiredInvalidQuantityReified.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.
requiredName: string -> Result<string,SignupError>value: stringstringAn abbreviation for the CLI type . Basic Types
(|>): '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.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
present: Constraint<^value>Alias for .
orError: 'a -> Result<'b,'c> -> Result<'b,'a>Replaces a failed constraint's violation with the supplied error. Defined here because Reified.Constraint does not depend on Reified.Result. value |> Constraint.guard present |> orError NameRequired
quantity: int -> Result<int,SignupError>value: intatLeast: 'a -> Constraint<'a>Alias for .
mapError: ('a -> 'b) -> Result<'c,'a> -> Result<'c,'b>Maps a failed constraint's violation with the supplied function. Defined here because Reified.Constraint does not depend on Reified.Result. value |> Constraint.guard (greaterThan 0) |> mapError InvalidQuantity