Functional Validation in F# Using Applicatives

by Ian Russel | Analyst/Architect/Developer

This is my entry for the F# Advent Calendar 2019 https://sergeytihon.com/tag/fsadvent/.

I'm using Railway-Oriented Programming (https://fsharpforfunandprofit.com/rop/) and was looking for an elegant, functional way to handle the conversion of an UnvalidatedType to a Result of ValidatedType or List of ValidationFailures (UnvalidatedUser -> Result< ValidatedUser, ValidationFailure list >). Having read Scott Wlaschin's excellent book (https://pragprog.com/book/swdddf/domain-modeling-made-functional), I knew that Applicatives were the functional answer to this problem.

This post is my interpretation of what I found.

Initial Solution

We start with a couple of simple record types and a discriminated union of potential validation failures:

open System
open System.Text.RegularExpressions

type UnvalidatedUser = {
    Name : string
    Email : string
    DateOfBirth : string
}

type ValidatedUser = {   
    Name : string
    Email : string
    DateOfBirth : DateTime
}

type ValidationFailure =
    | NameIsInvalidFailure
    | EmailIsInvalidFailure
    | DateOfBirthIsInvalidFailure

We then add a number of partial active patterns and functions to handle validating the individual values:

let (|ParseRegex|_|) regex str =
   let m = Regex(regex).Match(str)
   if m.Success then Some (List.tail 
[ for x in m.Groups -> x.Value ])
   else None

let (|IsValidName|_|) input =
    if input <> String.Empty then Some () else None

let (|IsValidEmail|_|) input =
    match input with
    | ParseRegex ".*?@(.*)" [ _ ] -> Some input
    | _ -> None

let (|IsValidDate|_|) input =
    let (success, value) = DateTime.TryParse(input)
    if success then Some value else None

let validateName input = // string -> Result<string, 
ValidationFailure list>
    match input with
    | IsValidName -> Ok input
    | _ -> Error [ NameIsInvalidFailure ]

let validateEmail input = // string -> Result<string, 
ValidationFailure list>
    match input with
    | IsValidEmail email -> Ok email
    | _ -> Error [ EmailIsInvalidFailure ]

let validateDateOfBirth input = // string -> Result
<DateTime, ValidationFailure list>
    match input with
    | IsValidDate dob -> Ok dob //Add logic for DOB
    | _ -> Error [ DateOfBirthIsInvalidFailure ]

Note that the validate functions return a list of ValidationFailure in the Error case. This makes concatenating them together later on much easier.

We now add a simple helper function to create a ValidatedUser and the main validate function:

let create name email dateOfBirth =
    { Name = name; Email = email; DateOfBirth = dateOfBirth }

let validate (input:UnvalidatedUser) : Result<ValidatedUser,
ValidationFailure list> =
    let validatedName = input.Name |> validateName
    let validatedEmail = input.Email |> validateEmail
    let validatedDateOfBirth = input.DateOfBirth |> 
validateDateOfBirth
    // create validatedName validatedEmail validatedDateOfBirth

The last line is commented out because it obviously won't compile as the types don't match but it is an aspiration to end up with a solution that gets close to this.

Helper Functions

We need to create two functions; the first to handle the non-Result to Result mapping of 'create' and 'validatedName' and the second to handle the rest. The first is such a common requirement that it is built into F# Core (from 4.1) -> Result.map. It looks very similar to this:

let map f xResult = // ('a -> 'b) -> Result<'a,'c> -> 
Result<'b,'c>
    match xResult with
     | Ok x -> Ok (f x)
     | Error ex -> Error ex

This only works in this situation because of partial application and that is equally important for the Ok path in our next helper function:

let apply fResult xResult = // Result<('a -> 'b), 
'c list> -> Result<'a,'c list> -> Result<'b,'c list>
    match fResult,xResult with
    | Ok f, Ok x -> Ok (f x)
    | Error ex, Ok _ -> Error ex
    | Ok _, Error ex -> Error ex
    | Error ex1, Error ex2 -> Error (List.concat [ex1; ex2])

The consequence of using partial application on the Ok track is that the partially applied function must always be the the first parameter to either function. This means that we can easily produce some working code to utilise these two helper functions that follow the rules to solve our requirement:

create
|> Result.map <| validatedName
|> apply <| validatedEmail
|> apply <| validatedDateOfBirth

We can show that this works correctly by writing some example test functions:

let validTest = 
    let actual = validate' { Name = "Ian"; 
Email = "hello@test.com"; DateOfBirth = "2000-02-03" } 
    let expected = Ok { Name = "Ian"; Email = "hello@test.com"; 
DateOfBirth = DateTime(2000, 2, 3)}
    expected = actual

let notValidTest = 
    let actual = validate' { Name = ""; Email = "hello"; 
DateOfBirth = "" }
    let expected = Error [ NameIsInvalidFailure; 
EmailIsInvalidFailure; DateOfBirthIsInvalidFailure ]
    expected = actual

Whilst our function works, it contains back pipes <| which Don Syme https://twitter.com/dsymetweets, the creator of F#, doesn't like, it is not considered the idiomatic way of writing applicatives in F# and doesn't really look anything like the result we aspire to achieve.

We can reduce some of the noise by removing all of the piping:

apply (apply (Result.map create validatedName) 
validatedEmail) validatedDateOfBirth

This is beginning to look like what we were originally looking to achieve. To progress further, we are going to use another of the really useful F# features; infix and prefix operators.

Infix/Prefix Operators

We have all seen the addition operator used as an infix:

let add x y = x + y

but it can also be used as a prefix:

let add x y = (+) x y

We can define our own operators:

let (<!>) = Result.map
let (<*>) = apply

We can replace Result.map with (<!>) and apply with (<*>) in our code:

(<*>) ((<*>) ((<!>) create validatedName) 
validatedEmail) validatedDateOfBirth

If we now use the infix versions we get:

((create <!> validatedName) <*> validatedEmail) 
<*> validatedDateOfBirth

We then remove the unnecessary brackets and we finally end up with:

create <!> validatedName <*> validatedEmail 
<*> validatedDateOfBirth

You can easily verify that it works by running the two tests we created earlier.

If the number of elements get too large, we can rewrite it like this:

create
<!> validatedName
<*> validatedEmail
<*> validatedDateOfBirth

I think you'll agree that this is an elegant solution to our original problem.

Putting It All Together

Let's finish off by showing the final codebase:

open System
open System.Text.RegularExpressions

type UnvalidatedUser = {
    Name : string
    Email : string
    DateOfBirth : string
}

type ValidatedUser = {   
    Name : string
    Email : string
    DateOfBirth : DateTime
}

type ValidationFailure =
    | NameIsInvalidFailure
    | EmailIsInvalidFailure
    | DateOfBirthIsInvalidFailure

let (|ParseRegex|_|) regex str =
   let m = Regex(regex).Match(str)
   if m.Success then Some (List.tail [ 
for x in m.Groups -> x.Value ])
   else None

let (|IsValidName|_|) input =
    if input <> String.Empty then Some () else None

let (|IsValidEmail|_|) input =
    match input with
    | ParseRegex ".*?@(.*)" [ _ ] -> Some input
    | _ -> None

let (|IsValidDate|_|) input =
    let (success, value) = DateTime.TryParse(input)
    if success then Some value else None

let validateName input = // string -> Result<string, 
ValidationFailure list>
    match input with
    | IsValidName -> Ok input
    | _ -> Error [ NameIsInvalidFailure ]

let validateEmail input = // string -> Result<string, 
ValidationFailure list>
    match input with
    | IsValidEmail email -> Ok email
    | _ -> Error [ EmailIsInvalidFailure ]

let validateDateOfBirth input = // string -> Result<DateTime, 
ValidationFailure list>
    match input with
    | IsValidDate dob -> Ok dob //Add logic for DOB
    | _ -> Error [ DateOfBirthIsInvalidFailure ]

let apply fResult xResult = // Result<('a -> 'b), 
'c list> -> Result<'a,'c list> -> Result<'b,'c list>
    match fResult,xResult with
    | Ok f, Ok x -> Ok (f x)
    | Error ex, Ok _ -> Error ex
    | Ok _, Error ex -> Error ex
    | Error ex1, Error ex2 -> Error (List.concat [ex1; ex2])

let (<!>) = Result.map
let (<*>) = apply

let create name email dateOfBirth =
    { Name = name; Email = email; DateOfBirth = dateOfBirth }

let validate (input:UnvalidatedUser) : Result<ValidatedUser,
ValidationFailure list> =
    let validatedName = input.Name |> validateName
    let validatedEmail = input.Email |> validateEmail
    let validatedDateOfBirth = input.DateOfBirth |> 
validateDateOfBirth
    create <!> validatedName <*> validatedEmail 
<*> validatedDateOfBirth

let validTest = 
    let actual = validate' { Name = "Ian"; 
Email = "hello@test.com"; DateOfBirth = "2000-02-03" } 
    let expected = Ok { Name = "Ian"; Email = "hello@test.com"; 
DateOfBirth = DateTime(2000, 2, 3)}
    expected = actual

let notValidTest = 
    let actual = validate' { Name = ""; Email = "hello"; 
DateOfBirth = "" }
    let expected = Error [ NameIsInvalidFailure; 
EmailIsInvalidFailure; DateOfBirthIsInvalidFailure ]
    expected = actual

I love functional programming with F#. Nothing makes me happier than finding simple, elegant solutions to interesting problems.

Further Reading

If you want a deeper dive into the wonders of applicatives and a lot more, I highly recommend that you read the following:

https://fsharpforfunandprofit.com/posts/elevated-world-3/#validation

https://blog.ploeh.dk/2018/10/01/applicative-functors/

Finally

Thanks to Sergey Tihon for https://sergeytihon.com/tag/newsf-weekly/ and for running the F# Advent Calendar, Scott Wlaschin for writing https://pragprog.com/book/swdddf/domain-modeling-made-functional and making https://fsharpforfunandprofit.com/ such an amazing resource plus special thanks to all of you in the F# community for being so awesome. :)

https://twitter.com/ijrussell

12/9/19
Blog 5/18/22

Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

Blog 5/17/23

Introduction to Functional Programming in F# – Part 10

Discover Agents and Mailboxes in F#. Build responsive applications using these powerful concurrency tools in functional programming.

Blog 10/11/22

Introduction to Functional Programming in F# – Part 5

Master F# asynchronous workflows and parallelism. Enhance application performance with advanced functional programming techniques.

Blog 10/1/22

Introduction to Functional Programming in F# – Part 4

Unlock F# collections and pipelines. Manage data efficiently and streamline your functional programming workflow with these powerful tools.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 7

Explore LINQ and query expressions in F#. Simplify data manipulation and enhance your functional programming skills with this guide.

Blog

Intro to Functional Programming in F# – Table of Content

Start learning functional programming in F# through practical examples. This blog series covers function composition, collections, recursion, computation expressions, and more.

Blog

Function Composition in F# with Unfriendly Functions

Explore how to handle unfriendly library functions in F# by using wrappers, higher-order functions, or inline solutions to keep pipelines clean and functional.

Blog 11/30/22

Introduction to Partial Function Application in F#

Partial Function Application is one of the core functional programming concepts that everyone should understand as it is widely used in most F# codebases.In this post I will introduce you to the grace and power of partial application. We will start with tupled arguments that most devs will recognise and then move onto curried arguments that allow us to use partial application.

Blog

Using GCP Cloud Functions with F#

Learn how to build and test Google Cloud Functions in F#, using dependency injection, configuration, and pub/sub messaging for real-world cloud apps.

Blog 9/15/22

Introduction to Functional Programming in F# – Part 3

Dive into F# data structures and pattern matching. Simplify code and enhance functionality with these powerful features.

Blog 9/13/22

Introduction to Functional Programming in F# – Part 2

Explore functions, types, and modules in F#. Enhance your skills with practical examples and insights in this detailed guide.

Blog 12/22/22

Introduction to Functional Programming in F# – Part 6

Learn error handling in F# with option types. Improve code reliability using F#'s powerful error-handling techniques.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 8

Discover Units of Measure and Type Providers in F#. Enhance data management and type safety in your applications with these powerful tools.

Blog 3/22/23

Introduction to Functional Programming in F# – Part 9

Explore Active Patterns and Computation Expressions in F#. Enhance code clarity and functionality with these advanced techniques.

Blog 7/12/23

Introduction to Functional Programming in F# – Part 11

Learn type inference and generic functions in F#. Boost efficiency and flexibility in your code with these essential programming concepts.

Blog 8/8/23

Introduction to Functional Programming in F# – Part 12

Explore reflection and meta-programming in F#. Learn how to dynamically manipulate code and enhance flexibility with advanced techniques.

Blog 12/3/21

Using Discriminated Union Labelled Fields

A few weeks ago, I re-discovered labelled fields in discriminated unions. Despite the fact that they look like tuples, they are not.

Blog 3/17/22

Using NLP libraries for post-processing

Learn how to analyse sticky notes in miro from event stormings and how this analysis can be carried out with the help of the spaCy library.

Blog

Celebrating Homai - Using AI for Good

Our colleague Aigiz Kunafin has achieved an outstanding milestone - importance of his side-project Homai was acknowledged by the “AI for Good” Initiative of United Nations.

Blog 8/7/20

Understanding F# Type Aliases

In this post, we discuss the difference between F# types and aliases that from a glance may appear to be the same thing.

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!