Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultInput Sources
Data is the source-agnostic boundary shape: Null, Text, Number, Bool, List, and
Object. Adapters turn common sources into Data, and one schema parses them all. (Data is its own dependency-free
package, useful beyond schemas, see its docs.)
The Schema
open Reified
open Reified.SchemaDSL
open Reified.ConstraintDSL
type Address = { City: string }
type Contact = { Kind: string; Value: string }
type Customer =
{ Name: string
Address: Address
Contacts: Contact list }
let addressSchema =
schema<Address> {
field _.City
construct (fun city -> { City = city })
}
let contactSchema =
schema<Contact> {
field _.Kind
field _.Value
construct (fun kind value -> { Kind = kind; Value = value })
}
let customerSchema =
schema<Customer> {
field _.Name
field _.Address {
withSchema addressSchema
}
field _.Contacts {
withSchema (Schema.listWith contactSchema)
constrain (minLength 1)
}
construct (fun name address contacts ->
{ Name = name; Address = address; Contacts = contacts })
}
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
FsLiveDocsGeneratedPage10_9FDB488BE757.AddressCity: stringstringAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage10_9FDB488BE757.ContactKind: stringValue: stringFsLiveDocsGeneratedPage10_9FDB488BE757.CustomerName: stringAddress: AddressContacts: Contact listlistThe 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.
addressSchema: Schema<Address>schema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
``.ctor``: Quotations.Expr<(Address -> string)> -> field<Address,string>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: Addressconstruct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
city: stringcontactSchema: Schema<Contact>``.ctor``: Quotations.Expr<(Contact -> string)> -> field<Contact,string>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: Contact_arg2: Contactkind: stringvalue: stringcustomerSchema: Schema<Customer>``.ctor``: Quotations.Expr<(Customer -> string)> -> field<Customer,string>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: Customer``.ctor``: Quotations.Expr<(Customer -> Address)> -> field<Customer,Address>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"
_arg2: CustomerWithSchema: SchemaFieldSteps.FieldInitial<Customer,Address> * Schema<'current> -> SchemaFieldSteps.FieldWorking<Customer,Address,'current>Supplies the schema transformed by the remaining operations in this field block.
``.ctor``: Quotations.Expr<(Customer -> Contact list)> -> field<Customer,Contact list>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"
_arg3: CustomerWithSchema: SchemaFieldSteps.FieldInitial<Customer,Contact list> * Schema<'current> -> SchemaFieldSteps.FieldWorking<Customer,Contact list,'current>Supplies the schema transformed by the remaining operations in this field block.
Reified.SchemaConstruction, composition, parsing, and checking for universal schemas.
listWith: Schema<'a> -> Schema<'a list>Describes a list using an explicit item schema.
Constrain: SchemaFieldSteps.FieldInitial<Customer,Contact list> * Constraint<Contact list> -> SchemaFieldSteps.FieldConfigured<Customer,Contact list>Adds a portable constraint to the field's current schema value.
minLength: int -> Constraint<^value>Alias for .
name: stringaddress: Addresscontacts: Contact listRequires & types
Requires: open Reified; open Reified.ConstraintDSL; open Reified.SchemaDSL (packages: Reified.Constraint, Reified.Schema)val addressSchema: Schema<Address>val contactSchema: Schema<Contact>val customerSchema: Schema<Customer>Here addressSchema and contactSchema are intentionally local value schemas. If Address and Contact declare
canonical intrinsic schemas, the Address line can use ordinary field, and the Contacts line can keep its field
block while omitting only withSchema: the list still resolves through Contact's canonical schema, but the block's
constrain (minLength 1) stays attached to the list field. That constraint constrains the length of the Contacts
list, so it belongs on the list field, not on Contact itself, which is a single record. A bare field _.Contacts
would drop the length check along with withSchema.
Nested fields expect object-shaped input and prefix their diagnostics with the field name; collection fields expect
Data.List, parse every item, accumulate every item error, and prefix diagnostics with the item index.
Each adapter below produces Data. The closing parse example uses the configuration input; the other examples
are isolated so their raw bindings do not collide.
HTTP Form-Like Input
Form posts and query strings are name/value pairs; repeated names become Data.List values:
open Reified
let raw =
Data.ofNameValues
[ "name", "Ada Lovelace"
"tag", "vip"
"tag", "beta" ] // repeated names accumulate into Data.List
Reifiedraw: DataReified.DataModuleofNameValues: (string * string) seq -> DataBuilds object-shaped data from name and value pairs.
Data.ofMap handles single-valued maps, and Data.ofNameValueCollection adapts
System.Collections.Specialized.NameValueCollection directly from ASP.NET-style APIs. Name/value sources are flat; use
the configuration or JSON adapters below when the input carries nested models or indexed collections.
CLI Arguments
open Reified
let raw = Data.ofCliArgs [ "--name"; "Ada Lovelace"; "--verbose"; "--no-color" ]
Reifiedraw: DataReified.DataModuleofCliArgs: string seq -> DataBuilds bounded nested data from CLI arguments; colons and double underscores delimit paths. Repeated paths collect values in argument order. Canonical decimal sections become sparse lists, using the same default limits as configuration conversion; positionals remain under _. Data.ofCliArgs [ "--Db:Port"; "5432"; "--Servers__0__Host=localhost" ]
--name value, --name=value, -n value, boolean flags, --no-name, and repeated options are supported; positional
arguments collect under the _ field. --Db:Port 5432 and --Db__Port=5432 create the same nested Db.Port
path by default. Sections use the canonical decimal index grammar and bounded expansion described below. Repeated
paths collect values in argument order; :/__ spellings of the same path share that repetition.
Key case
Parsing is case-sensitive unless configured with Schema.ignoreKeyCase. This option matches record field names
and aliases ordinal-ignore-case at every model depth; it does not rename fields or change arbitrary map keys,
tag values, prefix filtering, or compiled JSON codec name matching.
open Reified
open Reified.SchemaDSL
type PortSettings = { Port: int }
let settingsSchema = schema<PortSettings> { field _.Port; construct (fun port -> { Port = port }) }
let input =
[ Data.ofConfiguration [ "port", "1" ]
Data.ofEnvironment [ "PORT", "2" ]
Data.ofCliArgs [ "--port=5432" ] ]
|> Data.mergeAllWith Data.ordinalIgnoreCase
let parsed = input |> Schema.parseWith Schema.ignoreKeyCase settingsSchema
assert (parsed = Ok { Port = 5432 })
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.
FsLiveDocsGeneratedPage13_9FDB488BE757.PortSettingsPort: intintAn abbreviation for the CLI type . Basic Types
settingsSchema: Schema<PortSettings>schema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
``.ctor``: Quotations.Expr<(PortSettings -> int)> -> field<PortSettings,int>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: PortSettingsconstruct: 'constructor -> SchemaCeBuilder.ConstructorStep<'model,'constructor>Closes a record schema with a total constructor.
port: intinput: DataReified.DataModuleofConfiguration: (string * string) seq -> DataBuilds structured data from flattened configuration keys with bounded expansion. Only sections with all-canonical ASCII decimal child names become lists. Defaults are 10000 items per list, 100000 output nodes (including containers and null gaps), and 64 input path segments. Use ofConfigurationWith for explicit limits; exceeding one throws ArgumentException with the path.
ofEnvironment: (string * string) seq -> DataBuilds nested structured data from environment variable name and value pairs. Splits names on __, keeps their case, and turns canonical decimal sections into bounded lists. The caller reads the environment; see ofEnvironmentWith for a prefix, another separator, and the conflict rules. Data.ofEnvironment [ "Database__Port", "5432" ] // Data.Object [ "Database", Data.Object [ "Port", Data.Text "5432" ] ]
ofCliArgs: string seq -> DataBuilds bounded nested data from CLI arguments; colons and double underscores delimit paths. Repeated paths collect values in argument order. Canonical decimal sections become sparse lists, using the same default limits as configuration conversion; positionals remain under _. Data.ofCliArgs [ "--Db:Port"; "5432"; "--Servers__0__Host=localhost" ]
(|>): '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
mergeAllWith: System.Collections.Generic.IEqualityComparer<string> -> Data list -> DataFolds layers in precedence order with explicit object-name equality. [ Data.ofConfiguration [ "db:port", "1" ]; Data.ofEnvironment [ "DB__PORT", "2" ] ] |> Data.mergeAllWith Data.ordinalIgnoreCase
ordinalIgnoreCase: System.Collections.Generic.IEqualityComparer<string>The ordinal-ignore-case object-name comparer supported on .NET and Fable. layers |> Data.mergeAllWith Data.ordinalIgnoreCase
parsed: Result<PortSettings,SchemaErrors>Reified.SchemaConstruction, composition, parsing, and checking for universal schemas.
parseWith: (SchemaParseOptions -> SchemaParseOptions) -> Schema<'a> -> Data -> Result<'a,SchemaErrors>Parses structured data after configuring parser options.
ignoreKeyCase: SchemaParseOptions -> SchemaParseOptionsMatches input object keys ordinal-ignore-case at every model depth. Default parsing is case-sensitive. Equivalent input names deep-merge in source order, preserving first spelling/position and taking later values, like Data.merge. Schema names/aliases that collide under this comparison produce field.case.ambiguous. For layered settings, also merge layers with the same comparer. Data.ofEnvironment [ "PORT", "5432" ] |> Schema.parseWith Schema.ignoreKeyCase settingsSchema
(=): '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
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
Within an input object, equivalent names follow the merge rule: first spelling/position survive, later values win,
and object-valued sections merge recursively. Lists/scalars/null replace whole. A schema whose declared names or
aliases collide under this comparison is rejected with field.case.ambiguous, rather than assigning one input to
two fields. Default case-sensitive alias ambiguity checks remain unchanged.
Use the same comparer during layer merging, not only at parse time. An exact-name merge can overwrite an early
port in place while leaving a later PORT beside it; the parser cannot recover the original source order.
Data.mergeAllWith Data.ordinalIgnoreCase preserves later-source precedence, including across three such layers.
The comparer is supported on both .NET and Fable; use it instead of the BCL StringComparer property in portable code.
Schema.parseRetainingInputWith applies the same parse options while retaining input for redisplay; declared secret
field names and aliases are matched ordinal-ignore-case during redaction.
For credentials, use Secret fields, not a key-name redaction heuristic.
JSON Bodies With System.Text.Json
On .NET 8+ targets, adapt a parsed JsonDocument or JsonElement directly, the natural fit for ASP.NET Core
request bodies:
use! document = JsonDocument.ParseAsync request.Body
let raw = Data.ofJsonDocument documentJSON null, numbers, Booleans, arrays, and objects retain their corresponding Data cases. Number tokens keep their
exact lexical representation. The adapter uses the in-box System.Text.Json, so the package stays dependency-free.
Parse JSON portably
For the same lossless behavior on .NET and Fable, use Json.parseData from the installed Reified.Schema package:
open Reified
let raw = Json.parseData """{"name":"Ada","score":1.20e+3}"""
Reifiedraw: DataReified.JsonFunctions for compiling and running JSON codecs over built model schemas.
parseData: string -> DataParses one JSON value into source-neutral structured data. Preserves object field order, duplicate field names, and the original spelling of number tokens. This parser is available on .NET and Fable. It does not apply a model schema; use deserialize with a compiled codec when decoding directly to a schema-described model. Json.parseData "{\"name\":\"Ada\"}" // Data.Object [ "name", Data.Text "Ada" ] Thrown when is null. Thrown when the input is not one complete JSON value.
Under Fable, JSON.parse |> Data.ofJsonValue is also available when native JavaScript semantics are acceptable.
That route has already discarded duplicate fields and the original spelling of number tokens; use Json.parseData
when either distinction matters.
Configuration
Configuration keys use :-separated sections. A section with only canonical ASCII decimal child names
(0 or [1-9][0-9]*) becomes a collection:
let raw =
Data.ofConfiguration
[ "name", "Ada Lovelace"
"address:city", "London"
"contacts:0:kind", "email"
"contacts:0:value", "ada@example.com" ]
raw: DataReified.DataModuleofConfiguration: (string * string) seq -> DataBuilds structured data from flattened configuration keys with bounded expansion. Only sections with all-canonical ASCII decimal child names become lists. Defaults are 10000 items per list, 100000 output nodes (including containers and null gaps), and 64 input path segments. Use ofConfigurationWith for explicit limits; exceeding one throws ArgumentException with the path.
Requires & types
Requires: open Reified (packages: Reified.Data)val raw: DataLater pairs override earlier ones at the same path, matching .NET configuration layering: a repeated key keeps its
last value, and a later scalar or section replaces the earlier shape at that key. A section path written after a
scalar at the same key replaces the scalar with the section, and a later scalar at a section key replaces the section;
the one exception is a section key whose value is null, as IConfiguration.AsEnumerable() emits alongside a section's
children — that never overrides the children, so real layered IConfiguration output round-trips directly. Collections
come from numeric segments, never from repetition; repeated names as multi-value input is a wire convention that
belongs to ofNameValues. Numeric segments need not be contiguous: a sparse set such as contacts:0 and contacts:2
becomes a three-item list with Data.Null at the missing index 1.
Environment Variables
Environment variable names use __ between sections, the Microsoft.Extensions.Configuration convention. The adapter
takes the pairs rather than reading the environment, and an optional prefix selects and strips the application's
variables:
open Reified
let raw =
[ "SHOP__name", "Ada Lovelace"; "SHOP__address__city", "London"; "HOME", "/home/ada" ]
|> Data.ofEnvironmentWith (fun options -> { options with Prefix = Some "SHOP__" })
Reifiedraw: Data(|>): '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.DataModuleofEnvironmentWith: (DataEnvironmentOptions -> DataEnvironmentOptions) -> (string * string) seq -> DataBuilds nested structured data from environment variables selected and split by configured options. The configuring function receives the defaults (no prefix, separator __). A prefix is matched ordinally and stripped, so include its separator. Leaves are Data.Text (or Data.Null for a null string); names keep their case, and matching PORT to a field port is the schema's job (alias "PORT"). A section becomes a list only when every child name is 0 or matches [1-9][0-9]* using ASCII digits. Other names remain object fields: 00 never aliases 0. This is identical on .NET and Fable. Names are applied in ordinal order, because the environment has no meaningful order: a name that is also a section loses to the section (A=1 with A__B=2 gives A.B = "2" in either input order), and a repeated name keeps its last value. Names that are empty after the prefix or contain an empty segment are skipped. The root is always an object. Default Limits are 10000 items per list, 100000 output nodes including root/containers/gaps, and 64 path segments. Configure positive limits explicitly for larger trusted inputs; exceeding a limit throws ArgumentException identifying the path and bound. On .NET, pass Environment.GetEnvironmentVariables() as pairs; under Fable, pass the entries of process.env. System.Environment.GetEnvironmentVariables() |> Seq.cast<System.Collections.DictionaryEntry> |> Seq.map (fun entry -> string entry.Key, string entry.Value) |> Data.ofEnvironmentWith (fun options -> { options with Prefix = Some "MYAPP__" }) Thrown when the separator or a supplied prefix is empty, limits are nonpositive, or conversion exceeds a configured limit.
options: DataEnvironmentOptionsReified.DataEnvironmentOptionsHow Data.ofEnvironmentWith selects environment variables and splits their names into sections. Pass a configuring function to Data.ofEnvironmentWith; the value it receives is the default used by Data.ofEnvironment: no prefix, separator __, and limits of 10000 items per list, 100000 output nodes and 64 input path segments.
Prefix: string optionTakes only variables whose name starts with this text, compared ordinally (case-sensitive), and strips it. Include any separator in the prefix: Some "MYAPP__" turns MYAPP__Port into Port. None takes every variable.
Names keep their case, so a field matches SHOP__name but not SHOP__NAME unless it declares alias "NAME".
Sections use the same canonical decimal index grammar as configuration keys. Names such as 00, +1,
0x10 and 1_0 remain distinct object fields; they never alias an index. Both adapters default to 10,000 items
per list, 100,000 output nodes (including root, containers and null gaps) and 64 path segments. Exceeding a limit
throws ArgumentException before oversized expansion. Use Data.ofConfigurationWith or the environment options'
Limits record to configure positive bounds explicitly. See
environment variables for ordering/conflicts and
conversion limits for trusted-input overrides.
Layered Sources
Data.mergeAll combines sources before one parse, with later layers winning field by field. The schema then reports
every failure, whichever layer supplied the bad value:
open Reified
let layered =
Data.mergeAll
[ Json.parseData """{"name":"Ada Lovelace","address":{"city":"Paris"},"contacts":[]}"""
Data.ofEnvironment [ "address__city", "London" ]
Data.ofCliArgs [ "--name"; "Ada" ] ]
// name "Ada" from the command line, address.city "London" from the environment, contacts [] from the file
Reifiedlayered: DataReified.DataModulemergeAll: Data list -> DataMerges structured values in order with merge, so each later layer wins. Folds from the left starting at Data.Object [], so an empty list gives an empty object and a one-item list normalizes duplicate names in object layers; scalars and lists pass through unchanged. Order layers from lowest to highest precedence, typically file, then environment, then command line. Data.mergeAll [ Data.Object [ "port", Data.Number "80"; "host", Data.Text "localhost" ] Data.ofEnvironment [ "port", "8080" ] Data.ofCliArgs [ "--host"; "example.org" ] ] // Data.Object [ "port", Data.Text "8080"; "host", Data.Text "example.org" ] Thrown when is null.
Reified.JsonFunctions for compiling and running JSON codecs over built model schemas.
parseData: string -> DataParses one JSON value into source-neutral structured data. Preserves object field order, duplicate field names, and the original spelling of number tokens. This parser is available on .NET and Fable. It does not apply a model schema; use deserialize with a compiled codec when decoding directly to a schema-described model. Json.parseData "{\"name\":\"Ada\"}" // Data.Object [ "name", Data.Text "Ada" ] Thrown when is null. Thrown when the input is not one complete JSON value.
ofEnvironment: (string * string) seq -> DataBuilds nested structured data from environment variable name and value pairs. Splits names on __, keeps their case, and turns canonical decimal sections into bounded lists. The caller reads the environment; see ofEnvironmentWith for a prefix, another separator, and the conflict rules. Data.ofEnvironment [ "Database__Port", "5432" ] // Data.Object [ "Database", Data.Object [ "Port", Data.Text "5432" ] ]
ofCliArgs: string seq -> DataBuilds bounded nested data from CLI arguments; colons and double underscores delimit paths. Repeated paths collect values in argument order. Canonical decimal sections become sparse lists, using the same default limits as configuration conversion; positionals remain under _. Data.ofCliArgs [ "--Db:Port"; "5432"; "--Servers__0__Host=localhost" ]
A later null overrides rather than removes, and a later list replaces the earlier list whole. See
merge layered sources.
One Parse For All Of Them
Continuing with the configuration input above:
let parsed = Schema.parseRetainingInput customerSchema raw
match parsed.Result with
| Ok customer -> ignore customer
| Error diagnostics ->
// same paths for every source:
parsed.ErrorsFor "contacts[0].value" |> List.map SchemaError.render |> ignore
parsed: RetainedParseResult<Customer>Reified.SchemaConstruction, composition, parsing, and checking for universal schemas.
parseRetainingInput: Schema<'a> -> Data -> RetainedParseResult<'a>Parses source-neutral structured data while retaining it for redisplay and error lookup.
customerSchema: Schema<Customer>raw: DataResult: Result<'value,SchemaErrors>The parsed model or accumulated schema failures.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
customer: Customerignore: 'T -> unitIgnore the passed value. This is often used to throw away results of a computation. The value to ignore. ignore 55555 // Evaluates to ()
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
diagnostics: SchemaErrorsErrorsFor: string -> SchemaError listReturns schema errors attached exactly to the supplied structured data path.
(|>): '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
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
map: ('T -> 'U) -> 'T list -> 'U listBuilds a new collection whose elements are the results of applying the given function to each of the elements of the collection. The function to transform elements from the input list. The input list. The list of transformed elements. let inputs = [ "a"; "bbb"; "cc" ] inputs |> List.map (fun x -> x.Length) Evaluates to [ 1; 3; 2 ]
Reified.SchemaErrorModuleFunctions for lowering and rendering boundary schema failures.
render: SchemaError -> stringRenders a schema error as an English sentence. Constraint violations delegate to Violation.render, so there is one rendering catalogue. SchemaError.render (SchemaError.InvalidFormat "int") // "Expected int format."
Requires & types
Requires: open Reified (packages: Reified.Schema)val parsed: RetainedParseResult<Customer>From C#
Consume-don't-author: F# declares the schema, C# parses and reads diagnostics. Most adapters take plain .NET types
already, ofNameValueCollection takes NameValueCollection, ofCliArgs and ofData take ordinary
sequences and values, and call as plain static methods. ofMap and ofConfiguration take F#-only types (Map and
a sequence of F# tuples), so use their C#-friendly equivalents instead:
using Reified.Schema;
// ofMap's C# equivalent, takes IDictionary<string, string> instead of an F# Map:
Data raw = DataModule.ofDictionary(new Dictionary<string, string> { ["name"] = "Ada Lovelace" });
// ofConfiguration's C# equivalent, takes the pairs IConfiguration.AsEnumerable() already returns:
Data fromConfig = DataModule.ofConfigurationPairs(configuration.AsEnumerable());
RetainedParseResult<Customer, SchemaError> parsed = Schema.parseRetainingInput(customerSchema, raw);
if (parsed.IsValid)
{
Customer customer = parsed.Value;
}
else
{
var errors = parsed.Errors; // FSharpList<Diagnostic<SchemaError>>
}
Schema.parseWith takes an F# function for its configure parameter, which C# cannot pass a lambda to directly. Use
Schema.parseWithOptions, which takes a Func<Options, Options> instead:
var parsed = Schema.parseWithOptions(o => Schema.constructorErrorAt("end", o), dateRangeSchema, raw);