Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultLocalization
Localization is a rendering concern shared by Constraints and Schema. Constraints produce Violation values; Schema carries them alongside parse and structural failures in path-aware SchemaErrors. Both use the same Renderer, with Schema supplying the field path. Install Reified.Constraint for direct rules or Reified.Schema for structured input (it includes Constraint as a dependency). Open Reified; no separate localization package is required.
Start with the renderer examples below, then read Constraints and localization in schemas for a complete field example. Continue with context and fallback, custom rules, the key catalogue, and finally adding a language if you need translations.
A Violation carries no language. Built-in failures carry Reified-owned identities and operands; opaque failures
carry the prose their author wrote. Turning either into a sentence happens at the rendering edge, through a
Renderer.
If you never translate
Violation.render needs no renderer, no resources, and no setup:
open Reified
42
|> Constraint.check (Constraint.between 0 10)
|> Result.mapError Violation.render
// Error "expected a value between 0 and 10, but was 42"
Reified(|>): '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.
check: Constraint<'value> -> 'value -> Result<unit,Violation>Runs a constraint, returning why the value failed. let retryCount = Constraint.between 0 10 42 |> Constraint.check retryCount |> Result.mapError Violation.render
between: 'value -> 'value -> Constraint<'value>Requires a value inside the supplied inclusive bounds. let retryCount : Constraint<int> = Constraint.between 0 10
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.
Reified.ViolationModuleInspects, traverses, and renders violations.
render: Violation -> stringRenders a violation as an English sentence fragment with no trailing punctuation, keeping conjunction and alternative groups distinct. Violation.render (Atomic (Expected (PresenceAtom Present, None))) // "value must be present"
Requires & types
Requires: open Reified; open Reified.Result (packages: Reified.Constraint, Reified.Result)Renderer.english is the same idea with the renderer mechanics available, bare predicates, composed nouns, and
group joining, still without a resource file:
let violation =
match Constraint.check (Constraint.atLeast 13) 11 with
| Error violation -> violation
| Ok () -> failwith "unreachable"
violation |> Violation.fullMessage Renderer.english
// "value must be at least 13, but was 11"
violation: ViolationReified.ConstraintModuleCreates, executes, composes, and inspects constraints.
check: Constraint<'value> -> 'value -> Result<unit,Violation>Runs a constraint, returning why the value failed. let retryCount = Constraint.between 0 10 42 |> Constraint.check retryCount |> Result.mapError Violation.render
atLeast: 'value -> Constraint<'value>Requires a value greater than or equal to the supplied bound. let age : Constraint<int> = Constraint.atLeast 13
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
failwith: string -> 'TThrow a exception. The exception message. Never returns. let failingFunction() = failwith "Oh no" // Throws an exception true // Never reaches this failingFunction() // Throws a System.Exception
(|>): '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.ViolationModuleInspects, traverses, and renders violations.
fullMessage: Renderer -> Violation -> stringRenders a violation as a complete sentence fragment, with the attribute noun composed once. The noun wraps the finished message, never each leaf: a group of three failures still names the field once. With no attribute the contextual constraint.attribute.default supplies the noun, so an unattributed violation reads "Value must be present" rather than borrowing the document context. violation |> Violation.fullMessage (signup |> Renderer.attribute "name") // "Name must be present"
Reified.RendererModuleBuilds renderers and renders messages through them.
english: RendererA renderer that uses each catalogue's neutral English, with no resources at all. The default for tests, tools, and applications that never translate. It is not the same as Violation.render: this produces bare predicates that compose, while render keeps the legacy self-contained English exactly. violation |> Violation.fullMessage Renderer.english // "value must be present"
Requires & types
Requires: open Reified (packages: Reified.Constraint)val violation: ViolationTranslate four keys
A renderer is a key-to-template lookup. The smallest useful one is a map:
let french =
Map.ofList
[ "constraint.presence.present", "doit être renseigné"
"constraint.cardinality.minimum", "doit contenir au moins {minimum} éléments"
"constraint.format.email", "doit être une adresse e-mail"
"attribute.name", "Nom" ]
let renderer = Renderer.ofLookup french.TryFind
french: Map<string,string>Microsoft.FSharp.Collections.MapModuleContains operations for working with values of type .
ofList: ('Key * 'T) list -> Map<'Key,'T>Returns a new map made from the given bindings. The input list of key/value pairs. The resulting map. let input = [ (1, "a"); (2, "b") ] input |> Map.ofList // evaluates to map [(1, "a"); (2, "b")]
renderer: RendererReified.RendererModuleBuilds renderers and renders messages through them.
ofLookup: MessageLookup -> RendererA renderer backed by any key-to-template lookup. The portable constructor, and the one Fable applications use. let translations : Map<string, string> = Map.empty let renderer = Renderer.ofLookup translations.TryFind
TryFind: string -> string optionLookup an element in the map, returning a Some value if the element is in the domain of the map and None if not. The input key. The mapped value, or None if the key is not in the map. let sample = Map [ (1, "a"); (2, "b") ] sample.TryFind 1 // evaluates to Some "a" sample.TryFind 3 // evaluates to None
Requires & types
Requires: open Reified (packages: Reified.Constraint)val french: Map<string,string>val renderer: RendererKeys missing from the map fall back to Reified's neutral English. You can translate only the keys your application uses.
Look keys up in the catalogue.
The two messages
Violation.message renders the failure with no attribute noun; when the violation carries an actual value, it is
appended through the constraint.actual composition entry. Violation.fullMessage composes the attribute noun
around that once. Using the violation and renderer from above:
let field = renderer |> Renderer.context "signup" |> Renderer.attribute "name"
violation |> Violation.message field // "must be at least 13, but was 11"
violation |> Violation.fullMessage field // "Nom must be at least 13, but was 11"
field: Rendererrenderer: Renderer(|>): '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.RendererModuleBuilds renderers and renders messages through them.
context: string -> Renderer -> RendererAppends a document, model, form, or component segment. let signup = renderer |> Renderer.context "signup"
attribute: string -> Renderer -> RendererReplaces the attribute with one segment. Replacement, not append: a form-scoped renderer stays reusable for sibling fields. Schema supplies its typed path through Renderer.Advanced.attributePath instead. signup |> Renderer.attribute "name"
violation: ViolationReified.ViolationModuleInspects, traverses, and renders violations.
message: Renderer -> Violation -> stringRenders a violation as a localized predicate, with no attribute noun. Use this where a label already names the field — a form row, or a Schema result whose returned path identifies it. Conjunctions and alternatives join through the contextual constraint.group.* patterns; an actual value, when the violation carries one, is composed in through constraint.actual. violation |> Violation.message (renderer |> Renderer.context "signup" |> Renderer.attribute "name") // "must be at least 13, but was 11"
fullMessage: Renderer -> Violation -> stringRenders a violation as a complete sentence fragment, with the attribute noun composed once. The noun wraps the finished message, never each leaf: a group of three failures still names the field once. With no attribute the contextual constraint.attribute.default supplies the noun, so an unattributed violation reads "Value must be present" rather than borrowing the document context. violation |> Violation.fullMessage (signup |> Renderer.attribute "name") // "Name must be present"
Requires & types
Requires: open Reified (packages: Reified.Constraint)val field: RendererThe French map has no constraint.relation.atLeast entry, so the predicate falls back to Reified's neutral
English; the attribute.name entry does resolve, which is why the noun is Nom.
Use message where a label already names the field, a form row, or a Schema result whose returned path identifies
it. Use fullMessage for API payloads, logs, and anywhere the message stands alone.
The noun is composed once, never per leaf, so a group of three failures still names the field once:
let name : Constraint<string> =
Constraint.all [ Constraint.present; Constraint.lengthBetween 2 40 ]
"" |> Constraint.check name |> Result.mapError (Violation.fullMessage field)
// Error "Name must be present and must have a size between 2 and 40, but was 0"
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>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
lengthBetween: int -> int -> Constraint<^value>Requires a text or collection size inside the supplied inclusive bounds. let name : Constraint<string> = Constraint.lengthBetween 2 40
(|>): '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
check: Constraint<'value> -> 'value -> Result<unit,Violation>Runs a constraint, returning why the value failed. let retryCount = Constraint.between 0 10 42 |> Constraint.check retryCount |> 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.
Reified.ViolationModuleInspects, traverses, and renders violations.
fullMessage: Renderer -> Violation -> stringRenders a violation as a complete sentence fragment, with the attribute noun composed once. The noun wraps the finished message, never each leaf: a group of three failures still names the field once. With no attribute the contextual constraint.attribute.default supplies the noun, so an unattributed violation reads "Value must be present" rather than borrowing the document context. violation |> Violation.fullMessage (signup |> Renderer.attribute "name") // "Name must be present"
field: RendererRequires & types
Requires: open Reified; open Reified.Result (packages: Reified.Constraint, Reified.Result)val name: Constraint<string>Building a renderer
// Any key-to-template lookup. The portable constructor, and the one Fable uses.
Renderer.ofLookup translations.TryFind
// A .NET resource manager and one culture for lookup, plurals, and operand formatting.
Renderer.ofResourceManager resources culture
// Interface language and operand conventions separately: English text, German decimal separators.
Renderer.ofResourceManagerWithCultures resources uiCulture valueCulture
// CurrentUICulture and CurrentCulture, read on every render rather than captured.
Renderer.ofCurrentCulture resourcesRenderer.english needs no resources at all: every entry falls back to Reified's neutral English. It is the default
for tests, tools, and applications that never translate.
ofCurrentCulture is the one place ambient culture enters Reified. It reads the thread's cultures per render, so one
renderer registered as a singleton follows a per-request culture. Constraint execution itself stays free of ambient
effects.
Register a renderer as an ordinary immutable value. Reified introduces no renderer interface, no ambient registry, and no global configuration:
services.AddSingleton(Renderer.ofCurrentCulture resources) |> ignoreRecipes
Do not show the rejected value
By default a failure that carries an actual value reads "must be at least 13, but was 11". The value clause is its own entry, so removing it is one override:
let quiet = Map.ofList [ "constraint.actual", "{message}" ]
violation |> Violation.message (Renderer.ofLookup quiet.TryFind)
// "must be at least 13"
quiet: Map<string,string>Microsoft.FSharp.Collections.MapModuleContains operations for working with values of type .
ofList: ('Key * 'T) list -> Map<'Key,'T>Returns a new map made from the given bindings. The input list of key/value pairs. The resulting map. let input = [ (1, "a"); (2, "b") ] input |> Map.ofList // evaluates to map [(1, "a"); (2, "b")]
violation: Violation(|>): '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.ViolationModuleInspects, traverses, and renders violations.
message: Renderer -> Violation -> stringRenders a violation as a localized predicate, with no attribute noun. Use this where a label already names the field — a form row, or a Schema result whose returned path identifies it. Conjunctions and alternatives join through the contextual constraint.group.* patterns; an actual value, when the violation carries one, is composed in through constraint.actual. violation |> Violation.message (renderer |> Renderer.context "signup" |> Renderer.attribute "name") // "must be at least 13, but was 11"
Reified.RendererModuleBuilds renderers and renders messages through them.
ofLookup: MessageLookup -> RendererA renderer backed by any key-to-template lookup. The portable constructor, and the one Fable applications use. let translations : Map<string, string> = Map.empty let renderer = Renderer.ofLookup translations.TryFind
TryFind: string -> string optionLookup an element in the map, returning a Some value if the element is in the domain of the map and None if not. The input key. The mapped value, or None if the key is not in the map. let sample = Map [ (1, "a"); (2, "b") ] sample.TryFind 1 // evaluates to Some "a" sample.TryFind 3 // evaluates to None
Requires & types
Requires: open Reified (packages: Reified.Constraint)val quiet: Map<string,string>Translate only the rules you actually use
Translate the identities your application produces, leave the rest, and check what is missing with Adding a language.
Translate one of your own rules
Give the rule a key and it joins the same lookup, see Custom rules.
Do not translate the English sentence
Rendering an English message before translation loses the rule's identity and operands. Look up the identity to
cache translations, use consistent terminology, and format 13 for the target language.
Where next
- Context and fallback, scoping a renderer to a document and a field, and how lookup falls back.
- Custom rules, making your own constraints translatable.
- Advanced rendering, composition, interpolation, groups, plurals, and resolvers.
- The key catalogue, every key Reified can produce.
- Adding a language, the working order for a new translation, and proving coverage.
- Fable support, what the rendering edge does and does not do in JavaScript.
Schema renders its own parse and structural failures through these same mechanics, with the field path supplied for you: see Redisplay and field errors.