Error handling with Applicative Functors in F# and C#

In an object-oriented context a typical way to do error handling is to use the Notification Pattern described by Martin Fowler.

In functional programming (FP) the approach to error handling is very different. Error handling has to be done without mutating state and without the use of exceptions. The concept that is capable of this, and that is commonly used in FP is called “Applicative Functor”.

In this post, which is the first part of the series Functional error handling in F# and C#, we will cover all the basics that are needed to do functional error handling in F# and C#, and to understand Applicative Functors.

The F# and C# code samples from this post can be found here.

In the second part of the series we will look at a sample application for parsing command line arguments with error handling in F#.

Anyway, before we jump right into it, we will need some introduction.

Applicative Functors and error handling

We should consider the following aspect:

Applicative Functors in general have nothing to do with error handling!

A Functor is a concept from category theory that can be applied to programming. In programming an Applicative Functor is formed by a generic type together with an operation (usually called apply or ap) that obeys the Applicative Laws. That’s it! There is no error handling involved here.

The key of understanding is that the operation (apply) has its own implementation for each generic type. No matter for what type apply is implemented the signature will always be the same. This makes the concept very powerful, but we will come to that later. For error handling we will use the generic Result<_,_> type which, we could implement ourselves, but here we will use the ready-made type provided by the library Chessie. The details of error handling and the accumulation of messages in particular is implemented by the apply operation (and other operations as well) and therefore abstracted away from the consumer.

So again Applicative Functors in general are not related to error handling. They relate to error handling in the same way as e.g. IoC containers are related to logging – not very much.

Saying that ‘for error handling we should use Applicative Functors’ is almost as helpful as saying ‘to make a cake, we should use an oven’. To paraphrase Scott Wlaschin: In this post I will provide a recipe rather than presenting a tool.

Why C#

F# is much more suited than e.g. C# to demonstrate functional concepts as it is much more concise, it has pattern matching, everything is immutable by default, there are no NULL reference exceptions, it has better type inference and it supports tail recursion, just to name some advantages.

I imagine though, that it helps people to see new concepts being expressed with a syntax that they already know inside out, so that distraction will be minimized and the focus remains on the underlying concept. On the other hand C# is still the more dominant programming language for line of business applications. But anyway, functional practices and patterns can be implemented in C# and those C# projects can benefit from that very much.

Also when the same concepts are expressed in both C# and F# we will sometimes see that in C# it can be a little bit more painful. Right then we might appreciate the great features of languages like F# even more.

Yet another reason is that it shows how well .NET interoperability works.

The key type: Result<_,_>

The key type for error handling is Result<TSuccess, TMessage> which is provided by the Chessie library. It is defined as a so-called discriminated union with two cases. It represents either the result of a successful or a failed computation. The corresponding components are Ok of type Tuple<TSuccess, List<TMessage>> or Bad of type List<TMessage>.

This is the implementation in F#:

type Result<'TSuccess, 'TMessage> =
    | Ok of 'TSuccess * 'TMessage list
    | Bad of 'TMessage list

.NET interoperability

Chessie is written in F#. But it contains all the mumble jumble that is necessary to be easily used from C# projects.

Railway Oriented Programming

Once we’ve created an instance of Result we cannot easily apply normal functions anymore because therefore we would need to have easy access to the inner value. But since there is not always an inner value (a Result might be the outcome of a failed computation), we always have to handle both the Ok and the Bad case in our code.

We can think of a Result as a two-track data type. During the execution of our application we will either be on the success-track or on the failure-track and sometimes we will even change tracks. This brings us to the analogy of Railway Oriented Programming described by Scott Wlaschin.

Handling the union cases or tracks is exactly what the operations (such as map, bind or apply) provided by the Chessie library are doing for us. Choosing which lane we are on, whether we will stay there or not, and other related implementation details of error handling are abstracted away from us.

The elevated world of Result

When things are wrapped in a Result we can think of them as being lifted to an “elevated world”. There is the normal world and there are different elevated worlds where one of them is the “elevated world of Result<_,_>“. I like this analogy that was also created by Scott Wlaschin because it is free of any further implications or preconceptions, and it is well suited for intuitively expressing and describing what we are dealing with.

Values and functions have corresponding representations in both the normal and the elevated world.

Here are a few examples:

normal elevated
T Result<T, TMessage>
Func<T1, T2> Func<Result<T1, TMessage>, Result<T2, TMessage>>

There are also world-crossing functions e.g.:

Func<T1, Result<T2, TMessage>>

Here is the equivalent in F# syntax which is much more readable:

normal elevated
'a Result<'a, 'TMessage>
'a -> 'b Result<'a, 'TMessage> -> Result<'b, 'TMessage>

World-crossing:

'T1 -> Result<'T2, 'TMessage>

Create and extract

Creating a Result

Successful or failed results can be created like this:

F#

match isValid with
| true -> ok customer
| _ -> fail "an error occurred"

Because of F#’s great type inference we don’t have to specify any types in this example. If the compiler can’t infer one of the types of either 'TSuccess or 'TMessage, it will use object.

C#

if (isValid())
    return Result<Customer, string>.Succeed(customer);
else
    return Result<Customer, string>.FailWith("an error occurred");

Extracting the inner value

F#:

Extracting the inner value can be done with pattern matching in F#. We have to handle both cases, otherwise we will be warned by the compiler (which is actually very helpful):

match customer with
| ok (c,_) -> sprintf "%A" c
| errs -> sprintf "%A" errs

C#:

Unfortunately there is no pattern matching in C#. The hard way to extract the value would be to check for the IsOk tag, cast to either Result<_,_>.Ok or to Result<_,_>.Bad, and then access the value via the Item1 property. But this is very ugly and error prone.

Fortunately there is a better way by using an extension method which takes two functions, one for each case. The function for the Ok case takes two parameters of types TSuccess and List<TMessage> and the function for the Bad case takes a parameter of type List<TMessage>. Both functions have to return the same type. This pattern is called continuation passing style (CPS). Here is an example:

return customer.Either(
    ifSuccess: (c,_) => c.ToString(),
    ifFailure: errs => String.Join(", ", errs));

Switching back and forth between worlds

Once we are in the “elevated result world” we should stay there. It is bad practice to go back and forth between worlds. Lifting values into a Result or extracting values or error messages from a Result should only be done at the boundaries of our application or at least at the boundaries of the scope of our error handling task.

Programming with the Result type

lift (aka map)

With the lift function (also often called map) we can apply a function from the normal world to the inner value of a Result. If the result is a failure, the function will be ignored. In C# this operation is called Select.

In the following examples we multiply the inner value of a Result by two:

F#

let r2 = r |> Trial.lift ((*) 2)

Or the infix version:

let r2 = ((*) 2) <!> r

Examples

[<Test>]
let ``lift should multiply inner value of success by 2``() =
    ok 16
    |> Trial.lift ((*) 2) 
    |> shouldEqual (ok 32)
[<Test>]
let ``lift applied to a failure should fail``() =
    fail "error"
    |> Trial.lift ((*) 2) 
    |> shouldEqual (Bad [ "error" ])

C#

var r2 = r.Select(x => x * 2);

r2 will contain either the value multiplied by two or a failure.

bind

If we have functions that take “normal” arguments and return a Result (called cross world functions here), we can combine them with bind.

Let’s assume we have three functions, Validate, Update and Send, that all take a Customer instance as parameter and return Ok of type Customer if they succeed or Bad of type string if they fail.

These functions can be chained together with the bind operation. The specialty of bind is that the error message of the particular computation that fails will be propagated. However, if one operation fails, all subsequent computations will be bypassed as demonstrated by the following diagram:

alt text

Here is the code:

F#

validate customer
>>= update
>>= send

>>= is the infix version of bind.

Examples

Here are some tests that show the behavior:

[<Test>]
let ``bind should bypass after first error``() = 
    let validate c = ok c
    let update c = fail "update error"
    let send c = fail "send error"

    let customer = { id = 42; name = "John"; email = "john@example.com" }

    validate customer
    >>= update
    >>= send
    |> shouldEqual (Bad [ "update error" ])

[<Test>]
let ``bind should create valid customer if no failures``() = 
    let validate c = ok c
    let update c = ok c
    let send c = ok c

    let customer = { id = 42; name = "John"; email = "john@example.com" }

    validate customer
    >>= update
    >>= send
    |> shouldEqual (ok { id = 42; name = "John"; email = "john@example.com" })    

C#

In C# the extension method SelectMany represents the bind operation.

return Validate(customer)
    .SelectMany(Udapte)
    .SelectMany(Send)

Or we can use the LINQ query syntax like this:

return
    from v in Validate(customer)
    from u in Update(v)
    from s in Send(u)
    select s;

apply

Now let’s assume that we have a function from the “normal world”. But we want to apply the function to values from the elevated world. We can accomplish this with the apply operation.

apply will transform a function, that is wrapped inside a Result, into an elevated function. This is valuable because then the (elevated) function can be applied to the (elevated) values.

Before we can do this, though, we have to wrap the normal function inside a Result.

So a function can have several forms. It can be either normal, wrapped or elevated as shown in the following diagram:

normal wrapped in a Result elevated
F# 'a -> 'b Result<('a -> 'b), 'TMessage> Result<'a, 'TMessage> -> Result<'b, 'TMessage>
C# Func<T1, T2> Result<Func<T1, T2>, TMessage> Func<Result<T1, TMessage>, Result<T2, TMessage

The difference between a wrapped function and an elevated function is that the wrapped function contains the function ('a -> 'b) as the inner value. The elevated function on the other hand is a function from 'a wrapped in a Result to 'b wrapped in a Result. This transformation, from wrapped to elevated, can be done with apply.

Behavior

So again, technically speaking we can apply a normal function to elevated arguments with the help of apply. The behavioral difference to bind is that if at least one of the arguments is a failure, the result will accumulate all error messages from the first failure and from all other failed arguments. The computation will not be bypassed after the first failure as with the use of bind.

Usage

A Customer can be created with the constructor function create, that takes an id of int, a name of string, and an email address of string.

However, we should only be able to create a valid customer if id, name, and email are valid and comply to our business rules. Therefore we validate them and get a Result instance for each of those values:

name type
idResult Result<int, string>
nameResult Result<string, string>
emailResult Result<string, string>

F#

Here is the constructor function of Customer in F#:

> let create id name email = { id = id; name = name; email = email }
val create : id:int -> name:string -> email:string -> Customer

Now we can apply the create function to the validated values like this:

create <!> idResult <*> nameResult <*> emailResult

Note that this almost looks like normal function application only with operators placed in between arguments.

Examples

Let’s look at a code sample with a little more context. First we define some validation functions for id, name and email (with very simplified validation for demonstration purposes).

// simplified validation for demonstration purposes
let validateId id = if id > 0 then ok id else fail "id not valid"

let validateName (name: string) = if name.Length > 1 then ok name else fail "name not valid"

let validateEmail (email: string) = 
    let regex = new Regex(@"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
    match regex.IsMatch(email) with
    | true -> ok email
    | false -> fail "email not valid"

Then we can define the Create function of type int -> string -> string -> Result<Customer, string> to create a validated Customer instance.

let Create id name email = 
    let idResult = validateId id // type: int -> Result<int, string>
    let nameResult = validateName name // type: string -> Result<string, string>
    let emailResult = validateEmail email // type: string -> Result<string, string>

    let create = fun id name email -> { id = id; name = name; email = email }

    create 
    <!> idResult
    <*> nameResult
    <*> emailResult

Here are some tests that show the behavior:

[<Test>]
let ``apply valid inputs should create valid customer``() = 
    Create 42 "John" "john@example.com"
    |> shouldEqual (ok { id = 42; name = "John"; email = "john@example.com" })

[<Test>]
let ``apply invalid inputs should fail with accumulated messages``() = 
    Create -1 "John" "foo"
    |> shouldEqual (Bad [ "id not valid"; "email not valid" ])

How does it work?

What is really nice about using apply is that it almost looks like normal function application. The only difference in syntax is that there are operators placed in between the arguments.

The difference in behavior is that error messages are collected. If at least one of the parameters is a failure the result will also be a failure and all messages from all failures will be accumulated.

The <!> operator is the infix version of lift.

The <*> operator is the infix version of apply.

The reason for the first operation to be lift is that this is a convenient way to apply the first parameter, id.

<!> maps create over the inner value of idResult.

This operation returns a function that takes only two more parameters, and that is wrapped in a result:

Result<(string -> string -> Cusomter), string>

This (a function wrapped inside a Result) is exactly what we need to make use of apply, as explained above. So now we can use the <*> operator to apply the remaining two arguments, so that we retrieve a Result<Customer, string> at the end of the computation as the following diagram shows:

alt text

Let’s examine what happens going from step 1 to step 2. The outcome of 1 is a Result<(string -> (string -> Customer)), string>, a wrapped function. Since this function is curried, we can see it as a function from 'a to 'b ('a -> 'b), where 'a is of type string and 'b is of type (string -> Customer). apply now transforms this wrapped function into an elevated function:

(Result<string, string> -> Result<(string -> Customer), string>)

Also what happens in step 2 is that the elevated function is applied to the argument nameResult of type Result<string, string> which returns a Result<(string -> Customer), string>.

Step 3 is similar to step 2. And at the end we will get either the Customer instance or the accumulated error messages wrapped in a Result.

Another visualization that is very nice and simple can be found here.

Implementation

The main focus of this article is supposed to be on the usage of functional error handling. Although, the actual implementation is pretty straight forward. If you are interested check out the source code on GitHub.

C#

In C# we could do the same thing with extension methods. But as we already saw with bind we can use the LINQ query syntax with apply as well.

This would be the constructor function for a Customer:

internal static Customer Create(int id, string name, string email) { ... }

LINQ syntax

Now we can apply the elevated arguments with the LINQ query syntax like this:

return
    from id in idResult
    join name in nameResult on 1 equals 1
    join email in emailResult on 1 equals 1
    select Customer.Create(id, name, email);

It is actually a misuse of the standard LINQ query operator join. In this article Tomas Petricek explains how this works.

First we have to start with the from clause, combined with the first parameter. For all the remaining parameters we have to use the join clause. At the end of each join there has to be the equals clause which will be ignored. So we can always just write 1 equals 1. (I have seen 1 equals 1 being referred to as the “verbose semicolon” ;-).) At the end of the expression comes the select clause with the function that we want to apply.

C# functions are usually not defined as curried functions. One benefit of the LINQ syntax is that we do not have to transform Create to its curried form manually.

Extension methods

If we were to do this with extension methods it would go like this:

return idResult
    .Select(new Func<int, Func<string, Func<string, Customer>>>(id => name => email => Customer.Create(id, name, email)))
    .Apply(nameResult)
    .Apply(emailResult);

There are some drawbacks to this approach. One is that the Create function has to be curried manually. Another one is that the order of the first two arguments is reversed compared to the F# example, which doesn’t read very well.

Fortunately we can resolve these issues.

To get the order of arguments right, we can implement an extension method for Func<T1, T2> like this:

public static Result<T2, TMessage> Map<T1, T2, TMessage>(this Func<T1, T2> f, Result<T1, TMessage> result)
{
    return result.Select(f);
}

For currying we could implement the Create function curried, and as a static field on the definition side. Really nice about this is, that on the call side we can use it with pointfree syntax, which is very nice to read. But on the other hand this is not very common and makes readability on the definition side worse. So the best way is probably to write some extension methods for currying. Here is a class with all extension methods needed to curry function with up to 16 parameters.

For this example we will need this one:

public static Func<T1, Func<T2, Func<T3, T4>>> Curry<T1, T2, T3, T4>(this Func<T1, T2, T3, T4> f)
{
    return x1 => x2 => x3 => f(x1, x2, x3);
}

At this moment, as I’m writing this post, there is no Apply function for C# interoperability defined in Chessie. It is not urgently needed because we have other ways to express the functionality, like the LINQ syntax e.g., but if we want to use extension methods, we have to implement them ourselves. E.g. like this:

public static Result<T2, TMessage> Apply<T1, T2, TMessage>(this Result<Func<T1, T2>, TMessage> wrappedFunc, Result<T1, TMessage> result)
{
    var convertedFunc = wrappedFunc.Select(f => language-fsharpFunc<T1, T2>.FromConverter(x => f(x)));
    return Trial.apply(convertedFunc, result);
}

Now we can use this and get a much nicer syntax:

var create = new Func<int, string, string, Customer>(Customer.Create).Curry();

return create
    .Map(idResult)
    .Apply(nameResult)
    .Apply(emailResult);

What’s happening here is the exact same thing as with the F# example. For the sake of completeness, here is the (simplified) diagram from above with C# syntax:

alt text

What if we want the bind behavior?

Creating a Customer and bypassing after first failure is of course also possible (with the use of bind) and goes like this:

C#:

return
    from id in idResult
    from name in nameResult
    from email in emailResult
    select Customer.Create(id, name, email);

F#:

idResult 
>>= fun id -> nameResult 
>>= fun name -> emailResult 
>>= fun email -> ok (create id name email)

Conclusion

We have covered the basic usage of functional error handling with the help of the library Chessie in F# and C#. Also it should be clear now how the concept of Applicative Functors works and how it is related to error handling.

The key type is the Result<_,_> type.

The key operations are:

  • lift which transforms the inner value
  • bind which composes “cross-world” functions
  • apply which helps to apply normal functions to lifted values

We focused on the call side and the actual application of error handling. If you are interested in how this all is implemented check out the Chessie source code on GitHub.

Benefits of functional error handling

Composition, separation of concerns and scalability

Functional error handling embraces composition and separation of concerns. Small functions are isolated units that do not have any knowledge of their context, and are doing only and exactly one thing, with no dependencies. In the higher level modules they are composed with operations like lift, bind or apply, which makes it hard to weave in any other concerns.

The composition mechanism is always the same, which is monadic and applicative composition. Therefore this concept scales. There is no need to gradually add more and more layers of abstraction.

More predictable code

Exceptions ruin the ability to reason about the code because they behave like non-local goto statements. As there will never be any exceptions involved in functional error handling, the code will be more predictable.

Abstraction

Another benefit is that the aspects and details of error handling are totally abstracted away from the consumer.

Declarative programming

When programming with the Result type the code won’t be cluttered with the details of error handling. Because the concept embraces composition and higher-order functions the code will be very clean, readable and thus more declarative.

It is much saver

Because it is very hard and inconvenient to extract the inner value of a Result we are always encouraged to use the composition mechanisms of functional error handling. These mechanisms force the caller to always handle the success as well as the failure case. When doing imperative error handling, it is much easier to do things wrong. And then exceptions will be thrown. There is no way to avoid this potential risk. As a consequence the code will be cluttered with try catch clauses which makes readability and maintainability worse on the one hand. On the other hand the caller still can’t be absolutely sure that all possible exceptions will be caught. They can’t know just from the types whether exceptions might be thrown or not. So they might forget to protect against it and to handle the failure case.

Disclaimer

Doing functional error handling is not a silver bullet. There are still some issues, especially in C# which is not really a first-class functional language. Even though it is a little harder, it is still possible to induce implicit side effects. Therefore “functional” doesn’t mean “pure functional” but rather “in a functional style”. This also accounts to F# because F# isn’t a pure functional language either.

Another problem with C# is that all reference types including the Result type can be NULL. Also NULL can be wrapped inside a Result, which is also possible in F#, even though it is not common and strongly discouraged.

The use of NULL should therefore be completely avoided by convention.

Still, doing error handling the functional way makes it harder to use NULL and to have implicit side effects because of separation of concerns and the aspect of composition.

Some of the benefits that are described above might even become more obvious when looking at a real application. This we will do in the next part of the series: Functional error handling – parsing command line arguments in F#.

Resources