Starting with F# 4.1, there is a Result<'T,'TFailure> type which you can use for writing error-tolerant code which can be composed.


// The definition of Result in FSharp.Core
[<StructuralEquality; StructuralComparison>]
type Result<'T,'TError> = 
    | Ok of ResultValue:'T 
    | Error of ErrorValue:'TError


Note that the result type is a struct discriminated union, which is another feature introduced in F# 4.1. Structural equality semantics apply here.

The Result type is typically used in monadic error-handling, which is often referred to as Railway-oriented Programming within the F# community. The following trivial example demonstrates this approach.

// Define a simple type which has fields that can be validated
type Request = 
    { Name: string
      Email: string }

// Define some logic for what defines a valid name.
// Generates a Result which is an Ok if the name validates;
// otherwise, it generates a Result which is an Error.
let validateName req =
    match req.Name with
    | null -> Error "No name found."
    | "" -> Error "Name is empty."
    | "bananas" -> Error "Bananas is not a name."
    | _ -> Ok req

// Similarly, define some email validation logic.
let validateEmail req =
    match req.Email with
    | null -> Error "No email found."
    | "" -> Error "Email is empty."
    | s when s.EndsWith("") -> Error "No email from is allowed."
    | _ -> Ok req

let validateRequest reqResult =
    |> Result.bind validateName
    |> Result.bind validateEmail

let test() = 
    // Now, create a Request and pattern match on the result.
    let req1 = { Name = "Phillip"; Email = "" }
    let res1 = validateRequest (Ok req1)
    match res1 with
    | Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
    | Error e -> printfn "Error: %s" e
    // Prints: "My request was valid!  Name: Phillip Email:"

    let req2 = { Name = "Phillip"; Email = "" }
    let res2 = validateRequest (Ok req2)
    match res2 with
    | Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
    | Error e -> printfn "Error: %s" e
    // Prints: "Error: No email from is allowed."


As you can see, it's quite easy to chain together various validation functions if you force them all to return a Result. This lets you break up functionality like this into small pieces which are as composable as you need them to be. This also has the added value of enforcing the use of pattern matching at the end of a round of validation, which in turns enforces a higher degree of program correctness.

See Also

Discriminated Unions

Pattern Matching