Understanding F# applicatives and custom operators

Jonathan Channon discusses how he learnt about a slightly more advanced functional concept in F# — Applicatives.

[This post originally appeared on Jonathan’s personal blog -> https://blog.jonathanchannon.com/2020-07-17-understanding-fsharp-applicatives-custom-operators]

After discussing something with Ian Russell he suggested I take some time to read through another fine blog post he has written and understand F# applicatives and custom operators. I found myself in familiar territory when reading F# blog posts and it's something similar to the five stages of grief. Nod, Nod, I understand what's going on, Umm, WTF is going on. As Ian did in his Intro to F# series he sets out a simple domain problem and goes about how to address it. We want to return a ValidatedUser from a function but if the user fails validation we return a list of validation errors.

The code in the blog post was pretty self explanatory until, it wasn't, which I have pasted below:

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:string) =
    let (success, value) = DateTime.TryParse(input)
    if success then Some value else None

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

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

let validateDateOfBirth input = // string -> Result
    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 =
    let validatedName = input.Name |> validateName
    let validatedEmail = input.Email |> validateEmail
    let validatedDateOfBirth = input.DateOfBirth |> validateDateOfBirth
    // create validatedName validatedEmail validatedDateOfBirth

As you can see, there is commented out code on the last line because he has lined up the 3 arguments that are required to call the create function but calling it as-is won't work because the function takes in string,string,DateTime and we have Result<string, ValidationFailure list>, Result<string, ValidationFailure list>,Result<DateTime, ValidationFailure list>. As we know from my previous blog post we can use the Result.map function to do this sort of thing.

I will skip to the solution to this and work backwards because this is where I started to scratch my head a lot! Luckily the F# Software Foundation slack channel helped a lot, in particular Paul Blasucci.

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

From the last blog post I showed how to call functions in a chain of functions where Result types needed to be unwrapped and their values passed to the next function. So my first thought looking at this was validatedName is a value not a function so how is Result.map working? I also didn't quite understand the precedence of |> and |< data-preserve-html-node="true" how that worked. As part of my investigation, or some may say my learning and understanding, I was told Don Syme regretted making the back pipe and that using forward and back pipes together can make code unreadable. The take away there is to be careful about it's usage. The good thing here is that we only have one usage of it but it still didn't make sense to me. So I tried to split it up:

let foo = create |> Result.map <| validatedName

I still didn't quite get it, foo is a type of Result<(string -> DateTime -> ValidatedUser), ValidationFailure list> which means it's taken the name argument and now wants the email and date of birth passed to it. I understood partial application but still it didn't click. I went back to the previous blog post and looked at what the function signature of Result.map is. It takes in a function and a Result<'a,'b>. If the Result is OK it calls the passed in function with the unwrapped Result of 'a and returns a Result type of Ok(fn a) otherwise it returns Error e Here's the code for it:

let map mapping result = match result with Error e -> Error e | Ok x -> Ok (mapping x)

I then went back to the line of code after being informed that |< data-preserve-html-node="true" will always get called after |>. So what we have is create is passed in as the function to call in Result.map and the Result type is the validatedName variable. PARTIAL APPLICATION!!! Ok I get it now!

So once I could see what was happening it was time to understand what the apply function was doing. The first argument is a Result type whose generic args were a function and a list of validation failures, the second argument was a Result type whose generic args were a value and a list of validation failures. What apply does is match the two Result types together to check for (Ok, Ok) or (Ok, Error) etc and on success call the unwrapped function of the first arg with the unwrapped value of the second arg.

What confused me here was F# compiler magic. Now I knew about partial application but what I didn't understand was that when you assign a variable by calling a function using partial application the resulting type is not the result of the function being called. It's just a type of the function with one less argument to call, the compiler knows when to call the actual function once all arguments have been passed to it. What the function is doing is chaining argument calls to a partial application function. So we can see:

let foo = create |> Result.map <| validatedName // Result.map create validatedName
let bar = foo |> apply <| validatedEmail // apply foo validatedEmail
let baz = bar |> apply <| validatedDateOfBirth // apply bar validatedDateOfBirth

baz now is the final result of a call to execute the create function.

We can then remove the lets above and get to the final solution I mentioned previously:

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

As mentioned above it's advised not to use back pipes and so what we could end up with is:

create  validatedName <*> validatedEmail <*> validatedDateOfBirth

If like me you were totally confused by this then please see Ian's blog post for a full explanation but here's my take away.

As we saw above the apply function takes an "elevated" function and "elevated" value and then calls the function with the value and returns an elevated result. So we know in long hand version we have:

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

We can also use operators to replace function names to tidy things up so we end up with:

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

And we can make the above look like:

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

This hopefully all makes sense, but now a slight lesson in math notation which blew my mind. We know the signature 1 + 1 = 2. However, + is actually a function that takes two numbers, so in that regard what you have known since you were aged three should look like + 1 1 if we were to apply common programming signatures. Interestingly, you could also call the + function like 1 1 +. Where the + sits in the signature is called notation. Typically programming languages will use "prefix notation" function arg1 arg2 and some may use "postfix notation" arg1 arg2 function and arithmetic generally uses "infix notation" arg1 function arg2. However, in F# you can use "infix notation" which looks like the typical 1 + 1 signature which is let add x y = x + y. If we replace x + y knowing that the + is the function we can go from "prefix notation" (<!>) create validatedName to "infix notation" create <!> validatedName and apply it to our functions above. As we apply the calls to our infixed functions what we end up with is:

create  validatedName <*> validatedEmail <*> validatedDateOfBirth

This looks much neater than apply (apply (Result.map create validatedName) validatedEmail) validatedDateOfBirth but it does take a bit of learning and re-thinking to work out how the final solution create <!> validatedName <*> validatedEmail <*> validatedDateOfBirth actually works. I know this has been quite a learning curve for me but thankfully there are resources, people in the F# community and colleagues (thanks Ian!) that are keen to help and I thank them very much for this!

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.

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 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 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 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 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 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 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/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 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 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 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 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 9/27/22

Creating solutions and projects in VS code

In this post we are going to create a new Solution containing an F# console project and a test project using the dotnet CLI in Visual Studio Code.

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.

Training

Getting more from Jira Workflows (Data Center)

Over the course of "Getting More from Jira Workflows (Data Center)" training participants learn about common status and transition properties, advanced workflow functionalities and how to configure them.

Service

Customer Experience​ & Retention​

Promote long-term customer relationships through targeted retention strategies based on excellent customer experience.

Blog 3/12/21

Introduction to Web Programming in F# with Giraffe – Part 3

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Training

Getting More from Jira Workflows (Cloud)

Over the course of the "Getting More from Jira Workflows (Cloud)" training participants will learn about common status and transition properties and advanced workflow functionalities and how to configure them.

Training

Getting More from Jira Workflows (Cloud)

Over the course of the "Getting More from Jira Workflows (Cloud)" training participants will learn about common status and transition properties and advanced workflow functionalities and how to configure them.

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!