Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultCustom rules
Constraint.custom hands Reified prose and nothing else, so prose is all Reified can give back: it renders verbatim in
every language. Name a key and the rule becomes translatable:
open Reified
let isValidIsbn (value: string) =
value.Length = 13
let isbn =
Constraint.customLocalized
"books.isbn.invalid"
"must be a valid ISBN"
isValidIsbn
let isbnWithLength =
Constraint.customLocalizedWith
"books.isbn.invalid"
"must be a valid ISBN"
(Map.ofList [ "expectedLength", ConstraintValue.Integer 13L ])
isValidIsbn
// Untranslated, the prose is the fallback.
Constraint.check isbn "123"
|> Result.mapError Violation.render
// Error "must be a valid ISBN"
// With a lookup, the same key renders the translation.
let translations =
Map.ofList [ "books.isbn.invalid", "doit être un ISBN valide" ]
Constraint.check isbn "123"
|> Result.mapError (Violation.renderWith (fun descriptor ->
translations
|> Map.tryFind (MessageDescriptor.key descriptor)
|> Option.defaultValue "must be a valid ISBN"))
// Error "doit être un ISBN valide"
ReifiedisValidIsbn: string -> boolvalue: stringstringAn abbreviation for the CLI type . Basic Types
Length: int(=): '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
isbn: Constraint<string>Reified.ConstraintModuleCreates, executes, composes, and inspects constraints.
customLocalized: string -> string -> ('value -> bool) -> Constraint<'value>Runs an arbitrary predicate, reporting the supplied prose and the author's own catalogue key when it fails. Opaque exactly as custom is — the key names a message, not a rule, and claims nothing inspectable. What it buys is translation: a failure from plain custom projects as verbatim prose, which no resource system can look up, so an application with localization and one custom rule would otherwise have one permanently untranslatable message. The prose is still required, and remains the default English rendering. Reified supplies no key of its own here; only an author who has a catalogue can name an entry in it. let isValidIsbn (isbn: string) = isbn.Length = 13 in Constraint.customLocalized "books.isbn.invalid" "must be a valid ISBN" isValidIsbn
isbnWithLength: Constraint<string>customLocalizedWith: string -> string -> Map<string,ConstraintValue> -> ('value -> bool) -> Constraint<'value>Runs an arbitrary predicate, reporting the supplied prose plus a catalogue key and named arguments a translation can interpolate. The arguments are fixed at construction, which is what a rule's own operands are: an ISBN rule's expected length does not vary per value. A failure that must report something computed from the value takes customWith and builds its own violation. No plural operand is inferred. Ordinary .one/.other lookup applies only where a catalogue declares an operand, and guessing one from an argument's name or value would silently change which key a translator has to supply. let isValidIsbn (isbn: string) = isbn.Length = 13 in Constraint.customLocalizedWith "books.isbn.invalid" "must be a valid ISBN" (Map.ofList [ "expectedLength", ConstraintValue.Integer 13L ]) isValidIsbn
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")]
Reified.ConstraintValueThe closed set of operand and actual-value representations a constraint may carry across a description, diagnostic, or localization boundary. A value is admitted only when the representation is lossless in the semantics Reified's runtime diagnostics and exporters use. Semantic sorts keep their own case rather than being flattened into Text because their wire rendering happens to be textual: an instant and the string spelling it are different facts, and an interpreter that cannot tell them apart cannot decide whether wire equality substitutes for typed equality. Guid and TimeSpan are reached through typed dispatch rather than a runtime type test. Fable erases a Guid to a plain string and a TimeSpan to a number, so a boxed type test silently labels them Text and Integer there while .NET labels them correctly — the same constraint meaning two different things per platform. ConstraintValue.ofOperand resolves the overload at the call site, where the type is still known, so both platforms agree. Values outside this set are never boxed through the public surface. The constraint still executes against its private typed closure; the atom describes and fails as UnsupportedOperand instead. This is not a solver literal theory. A later proof phase declares its own numeric, date, and string sorts and adds a translation from the cases here; nothing may be added to this type for the solver alone.
IntegerAn integral value that fits a signed 64-bit integer.
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
(|>): '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.
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"
translations: Map<string,string>renderWith: (MessageDescriptor -> string) -> Violation -> stringRenders a violation through a caller-supplied lookup, keeping the same grouping and separators render uses. The whole localization path in one call. toMessageTree remains available for a translator that needs to control word order across a group; this is for the common case, where a resource lookup per message is the entire job and matching the tree by hand is pure ceremony. Verbatim leaves — author prose with no catalogue key — are passed through untranslated, because there is nothing to look up. violation |> Violation.renderWith (fun descriptor -> resources.Format(descriptor.Key, descriptor.Arguments))
descriptor: MessageDescriptortryFind: 'Key -> Map<'Key,'T> -> 'T 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 input map. The found Some value or None. let sample = Map [ (1, "a"); (2, "b") ] sample |> Map.tryFind 1 // evaluates to Some "a" sample |> Map.tryFind 3 // evaluates to None
Reified.MessageDescriptorModuleReads and builds message identities.
key: MessageDescriptor -> stringThe canonical unencoded key, exactly as authored. This is identity, not a lookup key. The encoded contextual resource keys a lookup receives come from Renderer.Advanced.lookupCandidates. let descriptor = MessageDescriptor.Advanced.create "books.isbn.invalid" Map.empty in MessageDescriptor.key descriptor // "books.isbn.invalid"
Microsoft.FSharp.Core.OptionModuleContains operations for working with options. Options
defaultValue: 'T -> 'T option -> 'TGets the value of the option if the option is Some, otherwise returns the specified default value. The specified default value. The input option. The option if the option is Some, else the default value. Identical to the built-in operator, except with the arguments swapped. (99, None) ||> Option.defaultValue // evaluates to 99 (99, Some 42) ||> Option.defaultValue // evaluates to 42
The prose stays required and becomes the fallback: an untranslated language still says something true. The key
takes the ordinary contextual chain, so for context signup and attribute book:
signup.book.books.isbn.invalid
signup.books.isbn.invalid
books.isbn.invalid
A key is segment ("." segment)*. An empty key or empty segment is rejected at construction, a malformed key
written in source is a defect, and failing at the call site beats failing at a rendering edge in whichever language
nobody tested. %, brackets, whitespace, and non-ASCII characters are exact input; you never pre-encode a key.
Custom constraints declare no plural operand, and Reified does not infer one from an argument's name or value.
Guessing would silently change which key a translator has to supply. If you need .one/.other for a custom rule,
select it in an advanced resolver.
Reified never invents a key for Constraint.custom prose. A key it made up would name a catalogue entry that does
not exist, and the lookup would fail in production, in the language you did not test.