Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultRefined Schemas
A type with a canonical schema needs only a bare field declaration. This minimal Email/Contact example shows
the result; the sections below build it step by step:
open Reified
open Reified.SchemaDSL
open Reified.Refinements
type Email = private Email of string
module Email =
let value (Email value) = value
let refinement = Refinement.define Constraint.email Email value
let create value = Refinement.create refinement value
type Email with
static member Schema(_: Email) : Schema<Email> = Schema.text |> Schema.refine Email.refinement
type Contact = { Email: Email }
module Contact =
let create email = { Email = email }
let contactSchema =
schema<Contact> {
field _.Email
construct Contact.create
}
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.
RefinementsFsLiveDocsGeneratedPage18_76B7E1D94D35.EmailEmailstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage18_76B7E1D94D35.EmailModulevalue: Email -> string_arg1: Emailvalue: stringrefinement: Refinement<string,Email>Reified.Refinements.RefinementCreates and applies reusable refinement definitions.
define: Constraint<'underlying> -> ('underlying -> 'refined) -> ('refined -> 'underlying) -> Refinement<'underlying,'refined>Defines a refinement from one constraint, a constructor, and the reverse projection. Compose several rules with Constraint.all before defining, and reach for Constraint.custom when the rule is an arbitrary predicate. Both produce an ordinary constraint, so there is no separate plural or check-taking constructor. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value)
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
email: Constraint<string>Requires text to match Reified's pragmatic email shape, ^[^@]+@[^@]+$. let contact : Constraint<string> = Constraint.email
create: string -> Result<Email,Violation>create: Refinement<'underlying,'refined> -> 'underlying -> Result<'refined,Violation>Constructs a refined value, reporting why the raw value was not admitted. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value) 3 |> Refinement.create retryCount |> Result.mapError Violation.render
Schema: Email -> Schema<Email>Reified.Schema`1Describes a typed value's portable structure and construction for schema interpreters. A schema records shape and construction metadata without tying that metadata to input parsing, diagnostics, validation, codecs, UI generation, or workflow execution. Primitive, collection, optional, union, refined, and record declarations all produce Schema<'value>. Record declarations use the schema<'value> { } computation expression. Each field may contain withSchema, constrain, constraints, refine, and validate operations before the declaration finishes with construct or constructResult.
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
refine: Refinement<'raw,'value> -> Schema<'raw> -> Schema<'value>Maps a raw schema through a reusable bidirectional refinement. The smart constructor runs during parsing. Inspection supplies the raw representation during checking and encoding.
FsLiveDocsGeneratedPage18_76B7E1D94D35.ContactEmail: EmailFsLiveDocsGeneratedPage18_76B7E1D94D35.ContactModulecreate: Email -> Contactemail: EmailcontactSchema: Schema<Contact>schema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
``.ctor``: Quotations.Expr<(Contact -> Email)> -> field<Contact,Email>Declares a field, deriving its camel-cased wire name from the property getter. type Signup = { Email: string } field (fun (s: Signup) -> s.Email) // wire name "email"
_arg1: Contactconstruct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
This is the form to prefer at use sites. Every built-in refined type from
Reified.Refinements works this way: NonBlankString,
FiniteFloat, UnitInterval, and NonEmptyList<_> all resolve without a withSchema, as
Getting Started shows.
Rules that need a parameter, such as a length range or a pattern, are constraints rather than types. They belong on the field, because the bounds are a property of this field rather than of the value:
field _.Name {
constrain Constraint.present
constrain (Constraint.lengthBetween 2 80)
}Expressing them this way means there is only ever one set of bounds, the schema's. An earlier BoundedString type
recorded the bounds it happened to be constructed under, so a value refined at 1..99 was still a BoundedString
when checked against a 2..80 schema, and the schema had to re-run its own bounds anyway.
Where a length or format rule should be part of a type, define your own refined type for it, as Lift universal constraints into the refinement shows. The test is whether any later operation relies on the rule; see When not to make a type.
Define the domain type
open Reified
open Reified.Refinements
type Email = private Email of string
module Email =
let value (Email value) = value
let refinement =
Refinement.define
Constraint.email
Email
value
let create value = Refinement.create refinement value
ReifiedRefinementsFsLiveDocsGeneratedPage17_76B7E1D94D35.EmailEmailstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage17_76B7E1D94D35.EmailModulevalue: Email -> string_arg1: Emailvalue: stringrefinement: Refinement<string,Email>Reified.Refinements.RefinementCreates and applies reusable refinement definitions.
define: Constraint<'underlying> -> ('underlying -> 'refined) -> ('refined -> 'underlying) -> Refinement<'underlying,'refined>Defines a refinement from one constraint, a constructor, and the reverse projection. Compose several rules with Constraint.all before defining, and reach for Constraint.custom when the rule is an arbitrary predicate. Both produce an ordinary constraint, so there is no separate plural or check-taking constructor. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value)
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
email: Constraint<string>Requires text to match Reified's pragmatic email shape, ^[^@]+@[^@]+$. let contact : Constraint<string> = Constraint.email
create: string -> Result<Email,Violation>create: Refinement<'underlying,'refined> -> 'underlying -> Result<'refined,Violation>Constructs a refined value, reporting why the raw value was not admitted. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value) 3 |> Refinement.create retryCount |> Result.mapError Violation.render
The email format is intrinsic to Email, so its refinement owns that constraint.
Apply constraints in the Schema DSL
Expand the field to expose its wire schema:
field _.Email {
withSchema Schema.text
constraints [ present; maxLength 80 ]
}Schema constraints remain available directly inside a field block. Here both constraints apply to the incoming
string, so interpreters can retain their metadata for diagnostics, forms, and generated schemas. The named Schema
constraints use the same executable metadata defined by Constraints.
Constraints preserve the value type, however. This block still contains a Schema<string>, while _.Email returns
Email; by itself, the declaration cannot complete the field.
Refine after constraining the raw value
Add refine after the raw-text constraints to perform that type transition. Email/Contact are redeclared here
(and again through the rest of this page) so each section's fragment stands on its own:
open Reified
open Reified.SchemaDSL
open Reified.ConstraintDSL
open Reified.Refinements
type Email = private Email of string
module Email =
let value (Email value) = value
let refinement = Refinement.define Constraint.email Email value
type Contact = { Email: Email }
module Contact =
let create email = { Email = email }
let contactSchema =
schema<Contact> {
field _.Email {
withSchema Schema.text
constraints [ present; maxLength 80 ]
refine Email.refinement
}
construct Contact.create
}
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
RefinementsFsLiveDocsGeneratedPage19_76B7E1D94D35.EmailEmailstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage19_76B7E1D94D35.EmailModulevalue: Email -> string_arg1: Emailvalue: stringrefinement: Refinement<string,Email>Reified.Refinements.RefinementCreates and applies reusable refinement definitions.
define: Constraint<'underlying> -> ('underlying -> 'refined) -> ('refined -> 'underlying) -> Refinement<'underlying,'refined>Defines a refinement from one constraint, a constructor, and the reverse projection. Compose several rules with Constraint.all before defining, and reach for Constraint.custom when the rule is an arbitrary predicate. Both produce an ordinary constraint, so there is no separate plural or check-taking constructor. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value)
Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
email: Constraint<string>Requires text to match Reified's pragmatic email shape, ^[^@]+@[^@]+$. let contact : Constraint<string> = Constraint.email
FsLiveDocsGeneratedPage19_76B7E1D94D35.ContactEmail: EmailFsLiveDocsGeneratedPage19_76B7E1D94D35.ContactModulecreate: Email -> Contactemail: EmailcontactSchema: Schema<Contact>schema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
``.ctor``: Quotations.Expr<(Contact -> Email)> -> field<Contact,Email>Declares a field, deriving its camel-cased wire name from the property getter. type Signup = { Email: string } field (fun (s: Signup) -> s.Email) // wire name "email"
``.ctor``: Quotations.Expr<('model -> 'target)> -> field<'model,'target>Declares a field, deriving its camel-cased wire name from the property getter. type Signup = { Email: string } field (fun (s: Signup) -> s.Email) // wire name "email"
_arg1: ContactWithSchema: SchemaFieldSteps.FieldInitial<Contact,Email> * Schema<'current> -> SchemaFieldSteps.FieldWorking<Contact,Email,'current>Supplies the schema transformed by the remaining operations in this field block.
Reified.SchemaConstruction, composition, parsing, and checking for universal schemas.
text: Schema<string>Describes text input.
Constraints: SchemaFieldSteps.FieldInitial<Contact,Email> * Constraint<Email> list -> SchemaFieldSteps.FieldConfigured<Contact,Email>Adds portable constraints to the field's inferred schema in declaration order.
present: Constraint<^value>Alias for .
maxLength: int -> Constraint<^value>Alias for .
Refine: SchemaFieldSteps.FieldWorking<Contact,Email,'raw> * Refinement<'raw,Email> -> SchemaFieldSteps.FieldWorking<Contact,Email,Email>Refines the current raw schema with an explicit refinement.
construct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
The operations run in declaration order:
withSchema Schema.textstarts withSchema<string>.constraintschecks facts that belong to the incoming text while preservingstring.refine Email.refinementconstructsEmail, producingSchema<Email>to match the getter.
A raw-text constraint must appear before refinement because it cannot be applied to the resulting Email value.
Keep one-off constraints at the schema boundary
Suppose only the billing form imposes an 80-character transport limit:
field _.BillingEmail {
withSchema Schema.text
constraints [ present; maxLength 80 ]
refine Email.refinement
}That limit belongs in this field block rather than in every Email. Email.refinement still enforces the intrinsic
email-format invariant for every construction path.
Use this inline form for boundary-specific restrictions. If a constraint must hold for every instance of the domain type, lift it into the refinement instead.
Attach an application constraint
There is no adapter step: Schema takes the same Constraint value you would check directly.
open Reified
let even : Constraint<int> =
Constraint.custom "must be an even quantity" (fun value -> value % 2 = 0)
Reifiedeven: 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.
custom: string -> ('value -> bool) -> Constraint<'value>Runs an arbitrary predicate, reporting the supplied prose when it fails. Opaque by construction. It executes and composes normally, may appear in Schema and refinements, and is documented honestly by exporters — but it is invisible to export enforcement and to proof, because an arbitrary host-language closure has no logical meaning to translate. No authored code or argument may claim inspectable logic. let isValidIsbn (isbn: string) = isbn.Length = 13 in Constraint.custom "must be a valid ISBN" isValidIsbn
value: int(%): ^T1 -> ^T2 -> ^T3Overloaded modulo operator The first parameter. The second parameter. The result of the operation. 29 % 5 // Evaluates to 4
(=): 'T -> 'T -> boolStructural equality The first parameter. The second parameter. The result of the comparison. 5 = 5 // Evaluates to true 5 = 6 // Evaluates to false [1; 2] = [1; 2] // Evaluates to true (1, 5) = (1, 6) // Evaluates to false
field _.Quantity {
constrain even
}An arbitrary predicate is opaque, so it runs during parsing and checking but is documented rather than enforced by
generated schemas. Composing built-ins instead, Constraint.multipleOf 2 here, keeps the rule inspectable, which is
what lets JSON Schema lower it and SchemaGen generate values that satisfy it. See
Interpreted and opaque for the trade.
Lift universal constraints into the refinement
If required presence and the length limit define every ContactEmail, put them beside the domain type instead:
open Reified
open Reified.Refinements
type ContactEmail = private ContactEmail of string
module ContactEmail =
let value (ContactEmail value) = value
let refinement =
Refinement.define
(Constraint.all [ Constraint.present; Constraint.email; Constraint.maxLength 254 ])
ContactEmail
value
let create value = Refinement.create refinement value
ReifiedRefinementsFsLiveDocsGeneratedPage17_76B7E1D94D35.ContactEmailContactEmailstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage17_76B7E1D94D35.ContactEmailModulevalue: ContactEmail -> string_arg1: ContactEmailvalue: stringrefinement: Refinement<string,ContactEmail>Reified.Refinements.RefinementCreates and applies reusable refinement definitions.
define: Constraint<'underlying> -> ('underlying -> 'refined) -> ('refined -> 'underlying) -> Refinement<'underlying,'refined>Defines a refinement from one constraint, a constructor, and the reverse projection. Compose several rules with Constraint.all before defining, and reach for Constraint.custom when the rule is an arbitrary predicate. Both produce an ordinary constraint, so there is no separate plural or check-taking constructor. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value)
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>Requires a value to be inhabited according to its shape. Whitespace-only text is blank, as are null text, a null or empty collection or map, None, ValueNone, and an empty Nullable. Blankness means .NET's whitespace set plus U+FEFF, which is what lets the rule be exported; see nonBlankPattern. The shape is selected from the return type, so a reusable binding needs its annotation: let requiredName : Constraint<string> = Constraint.present. Applied where the type is already known — inside an annotated rule, or to a schema — no annotation is needed. let requiredName : Constraint<string> = Constraint.present
email: Constraint<string>Requires text to match Reified's pragmatic email shape, ^[^@]+@[^@]+$. let contact : Constraint<string> = Constraint.email
maxLength: int -> Constraint<^value>Requires text or a collection to have at most the supplied size. let summary : Constraint<string> = Constraint.maxLength 280
create: string -> Result<ContactEmail,Violation>create: Refinement<'underlying,'refined> -> 'underlying -> Result<'refined,Violation>Constructs a refined value, reporting why the raw value was not admitted. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value) 3 |> Refinement.create retryCount |> Result.mapError Violation.render
The schema then carries the complete invariant through one value. isolated below because it redeclares
ContactEmail together with its canonical schema in one place; a fresh Message type stands in for Contact,
since this is a different, later invariant than the earlier Contact/Email example built:
open Reified
open Reified.Refinements
open Reified.SchemaDSL
type ContactEmail = private ContactEmail of string
module ContactEmail =
let value (ContactEmail value) = value
let refinement =
Refinement.define
(Constraint.all [ Constraint.present; Constraint.email; Constraint.maxLength 254 ])
ContactEmail
value
let contactEmailSchema : Schema<ContactEmail> =
Schema.text
|> Schema.refine ContactEmail.refinement
|> Schema.withFormat SchemaFormat.email
type ContactEmail with
static member Schema(_: ContactEmail) = contactEmailSchema
type Message = { Email: ContactEmail }
module Message =
let create email = { Email = email }
schema<Message> {
field _.Email
construct Message.create
}
ReifiedRefinementsReified.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.
FsLiveDocsGeneratedPage20_76B7E1D94D35.ContactEmailContactEmailstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage20_76B7E1D94D35.ContactEmailModulevalue: ContactEmail -> string_arg1: ContactEmailvalue: stringrefinement: Refinement<string,ContactEmail>Reified.Refinements.RefinementCreates and applies reusable refinement definitions.
define: Constraint<'underlying> -> ('underlying -> 'refined) -> ('refined -> 'underlying) -> Refinement<'underlying,'refined>Defines a refinement from one constraint, a constructor, and the reverse projection. Compose several rules with Constraint.all before defining, and reach for Constraint.custom when the rule is an arbitrary predicate. Both produce an ordinary constraint, so there is no separate plural or check-taking constructor. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value)
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>Requires a value to be inhabited according to its shape. Whitespace-only text is blank, as are null text, a null or empty collection or map, None, ValueNone, and an empty Nullable. Blankness means .NET's whitespace set plus U+FEFF, which is what lets the rule be exported; see nonBlankPattern. The shape is selected from the return type, so a reusable binding needs its annotation: let requiredName : Constraint<string> = Constraint.present. Applied where the type is already known — inside an annotated rule, or to a schema — no annotation is needed. let requiredName : Constraint<string> = Constraint.present
email: Constraint<string>Requires text to match Reified's pragmatic email shape, ^[^@]+@[^@]+$. let contact : Constraint<string> = Constraint.email
maxLength: int -> Constraint<^value>Requires text or a collection to have at most the supplied size. let summary : Constraint<string> = Constraint.maxLength 280
contactEmailSchema: Schema<ContactEmail>Reified.Schema`1Describes a typed value's portable structure and construction for schema interpreters. A schema records shape and construction metadata without tying that metadata to input parsing, diagnostics, validation, codecs, UI generation, or workflow execution. Primitive, collection, optional, union, refined, and record declarations all produce Schema<'value>. Record declarations use the schema<'value> { } computation expression. Each field may contain withSchema, constrain, constraints, refine, and validate operations before the declaration finishes with construct or constructResult.
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
refine: Refinement<'raw,'value> -> Schema<'raw> -> Schema<'value>Maps a raw schema through a reusable bidirectional refinement. The smart constructor runs during parsing. Inspection supplies the raw representation during checking and encoding.
withFormat: SchemaFormat -> Schema<'a> -> Schema<'a>Adds format metadata.
Reified.SchemaFormatModuleFunctions for creating and inspecting schema value format metadata.
email: SchemaFormatThe well-known email address format.
Schema: ContactEmail -> Schema<ContactEmail>FsLiveDocsGeneratedPage20_76B7E1D94D35.MessageEmail: ContactEmailFsLiveDocsGeneratedPage20_76B7E1D94D35.MessageModulecreate: ContactEmail -> Messageemail: ContactEmailschema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
``.ctor``: Quotations.Expr<(Message -> ContactEmail)> -> field<Message,ContactEmail>Declares a field, deriving its camel-cased wire name from the property getter. type Signup = { Email: string } field (fun (s: Signup) -> s.Email) // wire name "email"
_arg1: Messageconstruct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
The constraint-backed refinement now drives direct construction, Schema diagnostics, inspection metadata, and applicable wire interpreters.
Canonical refinement inference inside a field
A type may also contribute one canonical refinement for an underlying/destination pair. Same ContactEmail and
constraint set as the section above, contributing its refinement through a different member so bare refine can
find it:
open Reified
open Reified.Refinements
type ContactEmail = private ContactEmail of string
module ContactEmail =
let value (ContactEmail value) = value
let refinement =
Refinement.define
(Constraint.all [ Constraint.present; Constraint.email; Constraint.maxLength 254 ])
ContactEmail
value
type ContactEmail with
static member Refinement(_: string, _: ContactEmail) = ContactEmail.refinement
ReifiedRefinementsFsLiveDocsGeneratedPage21_76B7E1D94D35.ContactEmailContactEmailstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage21_76B7E1D94D35.ContactEmailModulevalue: ContactEmail -> string_arg1: ContactEmailvalue: stringrefinement: Refinement<string,ContactEmail>Reified.Refinements.RefinementCreates and applies reusable refinement definitions.
define: Constraint<'underlying> -> ('underlying -> 'refined) -> ('refined -> 'underlying) -> Refinement<'underlying,'refined>Defines a refinement from one constraint, a constructor, and the reverse projection. Compose several rules with Constraint.all before defining, and reach for Constraint.custom when the rule is an arbitrary predicate. Both produce an ordinary constraint, so there is no separate plural or check-taking constructor. type RetryCount = RetryCount of int let retryCount = Refinement.define (Constraint.between 0 10) RetryCount (fun (RetryCount value) -> value)
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>Requires a value to be inhabited according to its shape. Whitespace-only text is blank, as are null text, a null or empty collection or map, None, ValueNone, and an empty Nullable. Blankness means .NET's whitespace set plus U+FEFF, which is what lets the rule be exported; see nonBlankPattern. The shape is selected from the return type, so a reusable binding needs its annotation: let requiredName : Constraint<string> = Constraint.present. Applied where the type is already known — inside an annotated rule, or to a schema — no annotation is needed. let requiredName : Constraint<string> = Constraint.present
email: Constraint<string>Requires text to match Reified's pragmatic email shape, ^[^@]+@[^@]+$. let contact : Constraint<string> = Constraint.email
maxLength: int -> Constraint<^value>Requires text or a collection to have at most the supplied size. let summary : Constraint<string> = Constraint.maxLength 280
Refinement: string * ContactEmail -> Refinement<string,ContactEmail>Then an explicitly selected raw schema can use bare refine:
field _.Email {
withSchema Schema.text
refine
}Schema knows both string and ContactEmail at that declaration site. Use refine ContactEmail.refinement when a
local variant must be selected explicitly. Parsing and ordinary refinement construction remain named operations.
Choose the operation by meaning
Schema.refineconstructs an invariant-carrying destination and retains refinement metadata.Schema.convertperforms a total projected mapping.Schema.tryConvertperforms a fallible projected mapping returningSchemaError list.Schema.admitconstructs a domain model from a structured draft while preserving its fields.validateadds executable Schema behavior that no interpreter can read as metadata.
See Define Refined Types for domain-side definitions.