Our journey to F#: happy path coding

The application we are developing – it’s TimeRocket, just in case you forgot 😉 – has quite a lot of code that looks something like this (pseudo-code):

  • given is an identifier
  • load the data represented by the identifier if it exists
  • if the data could be loaded, validate if the desired action can be executed
  • if it can then continue
  • report back success or what failed

In C#, we often resort to exceptions because otherwise, we get a pyramid of doom.

So it’s time for one of the main reasons why we like F#: computation expressions.

There is some excellent content by Scott Wlaschin that explains computation expressions and the pyramid of doom way better than I can. So please, take a look at them before continuing:

Ah, you found your way back!

There is a great library that provides a lot of different computation expressions: FsToolkit.ErrorHandling.

Our code looks then something like this (simplified):

let addValueOnDimension
    queryActivityStructureEvents // dependency to get data from the storage
    activityStructureId // identifies the activity structure to extend
    dimensionId // the dimension on which we want to add a value
    value = // the value to add on the dimension

    // the asyncResult computation expression handles Async and Result
    // in the background so that we can focus on the logic here
    asyncResult {
        let! activityStructure =
            activityStructureId
            // event-sourcing: get all events for the activity structure
            |> queryActivityStructureEvents 
            // project the events, may result in Option.None
            |> Async.map ActivityStructure.projectSingle
            // we require the activity structure
            |> AsyncResult.requireSome "activity structure not found"

        // since this code is executed, we found the activity structure
        let! dimension =
            activityStructure
            // let's find the dimension, may result in Option.None
            |> ActivityStructure.tryFindDimension dimensionId
            // only continue if we found the dimension
            |> Result.requireSome "dimension not found"

        // we found the dimension, let's continue
        do! dimension.Values
            // check that the value is not already present
            |> List.tryFind (fun v -> v.ValueId = value.ValueId)
            // only continue if the value is not yet present
            |> Result.requireNone "value already defined on dimension"

        // execute code, we only get here if all the checks succeeded
        // otherwise an Error with an error message is returned
        addValue ...
    }

With computation expressions for Option, Result, Async, Task and all combinations of them, it is much simpler to write validation and error handling code in F# than it is in C#.

Computation expressions alone help us to get closer to all three goals we set for ourselves when starting the journey to F#:

  • reduced effort to create business value: the code is much simpler and therefore written faster, and reasoning about the code is simpler, too.
  • reduce mental load: no pyramid of doom, business logic doesn’t get distracted by error handling
  • less defects: defects have a much harder time to hide themselves in this simple code, compared to the C# we wrote before.

Happy “happy path”-coding everybody!

Find the next post in this series here.

Find all blog posts about our journey to F# here.

This blog post is made possible with the support of Time Rocket, the product this journey is all about. Take a look (German only).

About the author

Urs Enzler

1 comment

By Urs Enzler

Recent Posts