This is similar to Result<'T, 'Error> but with accumulative errors semantics, instead of short-circuit.
open System
open FSharpPlus
open FSharpPlus.Data
module MovieValidations =
type VError =
| MustNotBeEmpty
| MustBeAtLessThanChars of int
| MustBeADate
| MustBeOlderThan of int
| MustBeWithingRange of decimal * decimal
module String =
let nonEmpty (x:string) : Validation<VError list, string> =
if String.IsNullOrEmpty x
then Failure [MustNotBeEmpty]
else Success x
let mustBeLessThan (i: int) (x: string) : Validation<VError list, string> =
if isNull x || x.Length > i
then Failure [MustBeAtLessThanChars i]
else Success x
module Number =
let mustBeWithin (from, to') x =
if from<= x && x <= to'
then Success x
else Failure [MustBeWithingRange (from, to')]
module DateTime =
let classicMovie year (d: DateTime) =
if d.Year < year
then Success d
else Failure [MustBeOlderThan year]
let date (d: DateTime) =
if d.Date = d
then Success d
else Failure [MustBeADate]
type Genre =
| Classic
| PostClassic
| Modern
| PostModern
| Contemporary
type Movie = {
Id: int
Title: String
ReleaseDate: DateTime
Description: String
Price: decimal
Genre: Genre
} with
static member Create (id, title, releaseDate, description, price, genre) : Validation<VError list, Movie> =
fun title releaseDate description price -> { Id = id; Title = title; ReleaseDate = releaseDate; Description = description; Price = price; Genre = genre }
<!> String.nonEmpty title <* String.mustBeLessThan 100 title
<*> DateTime.classicMovie 1960 releaseDate <* DateTime.date releaseDate
<*> String.nonEmpty description <* String.mustBeLessThan 1000 description
<*> Number.mustBeWithin (0.0m, 999.99m) price
let newRelease = Movie.Create (1, "Midsommar", DateTime (2019, 6, 24), "Midsommar is a 2019 folk horror film written...", 1m, Classic) //Failure [MustBeOlderThan 1960]
let oldie = Movie.Create (2, "Modern Times", DateTime (1936, 2, 5), "Modern Times is a 1936 American comedy film...", 1m, Classic) // Success..
let titleToLong = Movie.Create (3, String.Concat (seq { 1..110 }), DateTime (1950, 1, 1), "11", 1m, Classic) //Failure [MustBeAtLessThanChars 100]
module Person =
type Name = { unName: String }
with static member create s = {unName = s }
type Email = { unEmail: String }
with static member create s = { unEmail = s }
type Age = { unAge : int }
with static member create i = { unAge = i }
type Person = {
name: Name
email: Email
age: Age }
with static member create name email age = { name = name; email = email; age = age }
type Error =
| NameBetween1And50
| EmailMustContainAtChar
| AgeBetween0and120
// Smart constructors
let mkName s =
let l = length s
if (l >= 1 && l <= 50)
then Success <| Name.create s
else Failure [NameBetween1And50]
let mkEmail s =
if String.contains '@' s
then Success <| Email.create s
else Failure [EmailMustContainAtChar]
let mkAge a =
if (a >= 0 && a <= 120)
then Success <| Age.create a
else Failure [AgeBetween0and120]
let mkPerson pName pEmail pAge =
Person.create
<!> mkName pName
<*> mkEmail pEmail
<*> mkAge pAge
// Examples
let validPerson = mkPerson "Bob" "bob@gmail.com" 25
// Success ({name = {unName = "Bob"}; email = {unEmail = "bob@gmail.com"}; age = {unAge = 25}})
let badName = mkPerson "" "bob@gmail.com" 25
// Failure [NameBetween1And50]
let badEmail = mkPerson "Bob" "bademail" 25
// Failure [EmailMustContainAtChar]
let badAge = mkPerson "Bob" "bob@gmail.com" 150
// Failure [AgeBetween0and120]
let badEverything = mkPerson "" "bademail" 150
// Failure [NameBetween1And50;EmailMustContainAtChar;AgeBetween0and120]
open FSharpPlus.Lens
let asMaybeGood = validPerson ^? Validation._Success
// Some ({name = {unName = "Bob"}; email = {unEmail = "bob@gmail.com"}; age = {unAge = 25}})
let asMaybeBad = badEverything ^? Validation._Success
// None
let asResultGood = validPerson ^. Validation.isoValidationResult
// Ok ({name = {unName = "Bob"}; email = {unEmail = "bob@gmail.com"}; age = {unAge = 25}})
let asResultBad = badEverything ^. Validation.isoValidationResult
// Error [NameBetween1And50;EmailMustContainAtChar;AgeBetween0and120]
module Email =
// ***** Types *****
type AtString = AtString of string
type PeriodString = PeriodString of string
type NonEmptyString = NonEmptyString of string
type Email = Email of string
type VError =
| MustNotBeEmpty
| MustContainAt
| MustContainPeriod
// ***** Base smart constructors *****
// String must contain an '@' character
let atString (x: string) : Validation<VError list, AtString> =
if String.contains '@' x then Success <| AtString x
else Failure [MustContainAt]
// String must contain an '.' character
let periodString (x: string) : Validation<VError list, PeriodString> =
if String.contains '.' x
then Success <| PeriodString x
else Failure [MustContainPeriod]
// String must not be empty
let nonEmptyString (x: string) : Validation<VError list, NonEmptyString> =
if not <| String.IsNullOrEmpty x
then Success <| NonEmptyString x
else Failure [MustNotBeEmpty]
// ***** Combining smart constructors *****
let email (x: string) : Validation<VError list, Email> =
result (Email x) <*
nonEmptyString x <*
atString x <*
periodString x
// ***** Example usage *****
let success = email "bob@gmail.com"
// Success (Email "bob@gmail.com")
let failureAt = email "bobgmail.com"
// Failure [MustContainAt]
let failurePeriod = email "bob@gmailcom"
// Failure [MustContainPeriod]
let failureAll = email ""
// Failure [MustNotBeEmpty;MustContainAt;MustContainPeriod]
namespace System
namespace FSharpPlus
namespace FSharpPlus.Data
type VError =
| MustNotBeEmpty
| MustBeAtLessThanChars of int
| MustBeADate
| MustBeOlderThan of int
| MustBeWithingRange of decimal * decimal
union case VError.MustNotBeEmpty: VError
union case VError.MustBeAtLessThanChars: int -> VError
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> =
int
union case VError.MustBeADate: VError
union case VError.MustBeOlderThan: int -> VError
union case VError.MustBeWithingRange: decimal * decimal -> VError
Multiple items
val decimal: value: 'T -> decimal (requires member op_Explicit)
--------------------
type decimal = Decimal
--------------------
type decimal<'Measure> =
decimal
Multiple items
type String =
interface IEnumerable<char>
interface IEnumerable
interface ICloneable
interface IComparable
interface IComparable<string>
interface IConvertible
interface IEquatable<string>
new: value: nativeptr<char> -> unit + 8 overloads
member Clone: unit -> obj
member CompareTo: value: obj -> int + 1 overload
...
<summary>Represents text as a sequence of UTF-16 code units.</summary>
--------------------
String(value: nativeptr<char>) : String
String(value: char array) : String
String(value: ReadOnlySpan<char>) : String
String(value: nativeptr<sbyte>) : String
String(c: char, count: int) : String
String(value: nativeptr<char>, startIndex: int, length: int) : String
String(value: char array, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : String
val nonEmpty: x: string -> Validation<VError list,string>
val x: string
Multiple items
val string: value: 'T -> string
--------------------
type string = String
Multiple items
module Validation
from FSharpPlus.Data
--------------------
type Validation<'error,'t> =
| Failure of 'error
| Success of 't
static member ( *> ) : x: Validation<'Error,'T> * y: Validation<'Error,'U> -> Validation<'Error,'U> (requires member ``+``)
static member (<!>) : f: ('T -> 'U) * x: Validation<'Error,'T> -> Validation<'Error,'U>
static member ( <* ) : x: Validation<'Error,'U> * y: Validation<'Error,'T> -> Validation<'Error,'U> (requires member ``+``)
static member (<*>) : f: Validation<'Error,('T -> 'U)> * x: Validation<'Error,'T> -> Validation<'Error,'U> (requires member ``+``)
static member (<.>) : f: Validation<'Error,('T -> 'U)> * x: Validation<'Error,'T> -> Validation<'Error,'U> (requires member ``+``)
static member (<|>) : x: Validation<'Error,'T> * y: Validation<'Error,'T> -> Validation<'Error,'T> (requires member ``<|>``)
static member Return: x: 'a2 -> Validation<'a3,'a2>
static member SequenceBiApply: t: Validation<'Error,'T> list -> Validation<'Error list,'T list> + 1 overload
static member get_Empty: unit -> Validation<'a2,'a3> (requires member Empty)
<summary>
A 'Validation' is either a value of the type 'error or 't, similar to 'Result'. However,
the 'Applicative' instance for 'Validation' accumulates errors using a 'Semigroup' on 'error.
In contrast, the Applicative for 'Result' returns only the first error.
A consequence of this is that 'Validation' is not a monad. There is no F#+ 'Bind' method since
that would violate monad rules.
</summary>
type 'T list = List<'T>
String.IsNullOrEmpty(value: string) : bool
union case Validation.Failure: 'error -> Validation<'error,'t>
union case Validation.Success: 't -> Validation<'error,'t>
val mustBeLessThan: i: int -> x: string -> Validation<VError list,string>
val i: int
val isNull: value: 'T -> bool (requires 'T: null)
property String.Length: int with get
<summary>Gets the number of characters in the current <see cref="T:System.String" /> object.</summary>
<returns>The number of characters in the current string.</returns>
val mustBeWithin: from: decimal * to': decimal -> x: decimal -> Validation<VError list,decimal>
val from: decimal
val to': decimal
val x: decimal
Multiple items
[<Struct>]
type DateTime =
new: year: int * month: int * day: int -> unit + 10 overloads
member Add: value: TimeSpan -> DateTime
member AddDays: value: float -> DateTime
member AddHours: value: float -> DateTime
member AddMilliseconds: value: float -> DateTime
member AddMinutes: value: float -> DateTime
member AddMonths: months: int -> DateTime
member AddSeconds: value: float -> DateTime
member AddTicks: value: int64 -> DateTime
member AddYears: value: int -> DateTime
...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>
--------------------
DateTime ()
(+0 other overloads)
DateTime(ticks: int64) : DateTime
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : DateTime
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : DateTime
(+0 other overloads)
val classicMovie: year: int -> d: DateTime -> Validation<VError list,DateTime>
val year: int
val d: DateTime
property DateTime.Year: int with get
<summary>Gets the year component of the date represented by this instance.</summary>
<returns>The year, between 1 and 9999.</returns>
val date: d: DateTime -> Validation<VError list,DateTime>
property DateTime.Date: DateTime with get
<summary>Gets the date component of this instance.</summary>
<returns>A new object with the same date as this instance, and the time value set to 12:00:00 midnight (00:00:00).</returns>
type Genre =
| Classic
| PostClassic
| Modern
| PostModern
| Contemporary
union case Genre.Classic: Genre
union case Genre.PostClassic: Genre
union case Genre.Modern: Genre
union case Genre.PostModern: Genre
union case Genre.Contemporary: Genre
Movie.Id: int
Movie.Title: String
Movie.ReleaseDate: DateTime
Movie.Description: String
Movie.Price: decimal
Multiple items
Movie.Genre: Genre
--------------------
type Genre =
| Classic
| PostClassic
| Modern
| PostModern
| Contemporary
val id: int
val title: string
val releaseDate: DateTime
val description: string
val price: decimal
val genre: Genre
type Movie =
{
Id: int
Title: String
ReleaseDate: DateTime
Description: String
Price: decimal
Genre: Genre
}
static member Create: id: int * title: string * releaseDate: DateTime * description: string * price: decimal * genre: Genre -> Validation<VError list,Movie>
val title: String
val description: String
module Number
from Type-validation.MovieValidations
val newRelease: Validation<VError list,Movie>
static member Movie.Create: id: int * title: string * releaseDate: DateTime * description: string * price: decimal * genre: Genre -> Validation<VError list,Movie>
val oldie: Validation<VError list,Movie>
val titleToLong: Validation<VError list,Movie>
String.Concat<'T>(values: Collections.Generic.IEnumerable<'T>) : string
(+0 other overloads)
String.Concat([<ParamArray>] values: string array) : string
(+0 other overloads)
String.Concat([<ParamArray>] args: obj array) : string
(+0 other overloads)
String.Concat(arg0: obj) : string
(+0 other overloads)
String.Concat(values: Collections.Generic.IEnumerable<string>) : string
(+0 other overloads)
String.Concat(str0: string, str1: string) : string
(+0 other overloads)
String.Concat(str0: ReadOnlySpan<char>, str1: ReadOnlySpan<char>) : string
(+0 other overloads)
String.Concat(arg0: obj, arg1: obj) : string
(+0 other overloads)
String.Concat(str0: string, str1: string, str2: string) : string
(+0 other overloads)
String.Concat(str0: ReadOnlySpan<char>, str1: ReadOnlySpan<char>, str2: ReadOnlySpan<char>) : string
(+0 other overloads)
Multiple items
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = Collections.Generic.IEnumerable<'T>
Name.unName: String
val s: String
Email.unEmail: String
Age.unAge: int
Person.name: Name
type Name =
{ unName: String }
static member create: s: String -> Name
Person.email: Email
type Email =
{ unEmail: String }
static member create: s: String -> Email
Person.age: Age
type Age =
{ unAge: int }
static member create: i: int -> Age
val name: Name
val email: Email
val age: Age
Multiple items
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
--------------------
type Error =
| NameBetween1And50
| EmailMustContainAtChar
| AgeBetween0and120
union case Error.NameBetween1And50: Error
union case Error.EmailMustContainAtChar: Error
union case Error.AgeBetween0and120: Error
val mkName: s: String -> Validation<Error list,Name>
val l: int
val length: source: 'Foldable<'T> -> int (requires member Length)
<summary>Gets the number of elements in the foldable.</summary>
<category index="11">Foldable</category>
<param name="source">The input foldable.</param>
<returns>The length of the foldable.</returns>
static member Name.create: s: String -> Name
val mkEmail: s: string -> Validation<Error list,Email>
val s: string
val contains: char: char -> source: string -> bool
<summary>
Does the source string contain the given character?
Use `String.isSubstring` to check for strings.
</summary>
static member Email.create: s: String -> Email
val mkAge: a: int -> Validation<Error list,Age>
val a: int
static member Age.create: i: int -> Age
val mkPerson: pName: String -> pEmail: string -> pAge: int -> Validation<Error list,Person>
val pName: String
val pEmail: string
val pAge: int
type Person =
{
name: Name
email: Email
age: Age
}
static member create: name: Name -> email: Email -> age: Age -> Person
static member Person.create: name: Name -> email: Email -> age: Age -> Person
val validPerson: Validation<Error list,Person>
val badName: Validation<Error list,Person>
val badEmail: Validation<Error list,Person>
val badAge: Validation<Error list,Person>
val badEverything: Validation<Error list,Person>
module Lens
from FSharpPlus
<summary>
Lens functions and operators
</summary>
val asMaybeGood: Error list option
val _Success: x: ('a -> 'b) -> (Validation<'a,'d> -> 'e) (requires member Map and member Return)
val asMaybeBad: Error list option
val asResultGood: Result<Person,Error list>
val isoValidationResult: x: 'a -> 'b (requires member Dimap and member Map)
val asResultBad: Result<Person,Error list>
module Email
from Type-validation
Multiple items
union case AtString.AtString: string -> AtString
--------------------
type AtString = | AtString of string
Multiple items
union case PeriodString.PeriodString: string -> PeriodString
--------------------
type PeriodString = | PeriodString of string
Multiple items
union case NonEmptyString.NonEmptyString: string -> NonEmptyString
--------------------
type NonEmptyString = | NonEmptyString of string
Multiple items
union case Email.Email: string -> Email
--------------------
type Email = | Email of string
type VError =
| MustNotBeEmpty
| MustContainAt
| MustContainPeriod
union case VError.MustContainAt: VError
union case VError.MustContainPeriod: VError
val atString: x: string -> Validation<VError list,AtString>
val periodString: x: string -> Validation<VError list,PeriodString>
val nonEmptyString: x: string -> Validation<VError list,NonEmptyString>
val email: x: string -> Validation<VError list,Email>
val result: x: 'T -> 'Functor<'T> (requires member Return)
<summary>
Lifts a value into a Functor. Same as return in Computation Expressions.
</summary>
<category index="2">Applicative</category>
val success: Validation<VError list,Email>
val failureAt: Validation<VError list,Email>
val failurePeriod: Validation<VError list,Email>
val failureAll: Validation<VError list,Email>