# Functional programming: Code with F# computation expressions

Learn when and how to use F# computation expressions to simplify your code.

Mar 21, 2024 • 5 Minute Read

One of my favorite features of the F# language is computation expressions.

Microsoft explains them as, “A convenient syntax for writing computations that can be sequenced and combined using control flow constructs and bindings. . . . Unlike other languages (such as do-notation in Haskell), they are not tied to a single abstraction, and do not rely on macros or other forms of metaprogramming to accomplish a convenient and context-sensitive syntax.”

In layman's terms, they're a really cool abstraction that allows a lot of super powerful transformations and operations in code flow. In this post, I explore functional programming in F# and share useful computation expressions you can use to streamline your code.

## Table of contents

## What is a computation expression in F#?

At its core, a computation expression is a type with members that takes values of some type and transforms those values in some way. There are all sorts of fancy functional programming terms that describe what's going on (you'll see the term monad thrown around a lot), but you don't really need to know all that to use and get value from computation expressions.

## Why use F# computation expressions?

There are many reasons to use computation expressions, but one of the main benefits is readability. In functional programming, it’s very common to use Discriminated Unions and record types. With these kinds of types, you’ll often perform mapping operations to transform values from one type to another (e.g. **SomeType<T>** -> **SomeType<U>**). Computation expressions allow us to perform common operations like this in a way that can really improve the readability of those operations.

Under the hood, all that a computation expression does is call functions using a special syntax. In fact, most computation expressions will have a module exposing the implementation functions that can be called directly for when that may make more sense.

But the computation expression syntax allows us to write some code a little more declaratively, and sometimes that’s quite beneficial. For example, the following functions using the FsToolkit.ErrorHandling library are fully equivalent:

` ````
// Without computation expression
let addResult: Result<int, string> =
tryParseInt "35"
|> Result.bind(fun x ->
tryParseInt “5”
|> Result.bind(fun y ->
tryParseInt "2"
|> Result.bind(fun z ->
add x y z
)
)
)
// With computation expression
let addResult: Result<int, string> = result {
let! x = tryParseInt "35"
let! y = tryParseInt "5"
let! z = tryParseInt "2"
return add x y z
}
```

## Built-in F# computation expressions

First, let’s explore some built-in F# computation expressions that you may already be using without even knowing.

### The seq computation expression

Probably the simplest example in F# is the built-in **seq** computation expression, which is exactly equivalent to the **IEnumerable<T>** interface in C#.

**seq** isn't actually a keyword in F#. It's just a built-in computation expression that defines the members necessary to create a sequence of values. It allows you to write code like this:

` ````
// yield statements are technically unnecessary here, but I'm including them for clarity in the comparison
let mySequence(): int seq =
seq {
yield 1
yield 2
yield 3
}
```

The equivalent C# code would be:

` ````
IEnumerable<int> MySequence()
{
yield return 1;
yield return 2;
yield return 3;
}
```

### Async and task

Other useful built-in computation expressions include** async** and **task**. That's right, unlike in C# where you rely on compiler magic to translate **async** to a state machine, in F# it's just computation expressions. (Well, the implementation of **task** does rely on a little compiler magic, but that's just an optimization.)

## Writing a custom computation expression: optional

One great example of a very simple custom computation expression is **optional**. What we're wanting is a cleaner way to deal with '**t option** values. If we have several things that may or may not be present, and we can only do a full operation if they're all there, we can use **optional **to make that code a lot cleaner.

### Before we begin: Understanding the Option<’T> type

For some brief background, F# has a built-in type called **Option<'T>**. The type definition looks like this:

` ````
type Option<'T> =
| Some of 'T
| None
```

This is commonly used to model situations where a value may or may not be present, similar to how **null** is used in many other languages (but F# tries to avoid the billion dollar mistake).

Thanks to the way the F# language works, we can use pattern matching on optional values to easily and cleanly handle things differently whether a value is present or not:

` ````
let doSomething (maybeValue: string option) =
match maybeValue with
| Some value -> printfn $"Value was {value}"
| None -> printfn "No value provided"
```

That's quite nice! We guarantee that a value is present and in the same operation retrieve the value. If the value isn’t there, we have a separate branch that handles that scenario.

Things may begin to get a little clunkier though, when multiple optional values are present, and we need to check for all of them to be present before proceeding. For example, say we have a piece of code like this:

` ````
let doSomething (a: string option) (b: int option) (c: float option) =
match a with
| Some a' ->
match b with
| Some b' ->
match c with
| Some c' ->
doSomethingWithAll a' b' c' |> Some
| None -> None
| None -> None
| None -> None
```

There's a lot of unwrapping going on here. It's very explicit what is going on here, but it's also pretty clunky to read through. In this example, we really are only able to proceed if all of the optional values are **Some(x)**, otherwise we just want to return **None**.

It would be nice if we could optimize our code for the "happy path" and just handle the **None** case at the end. That's where the **optional** computation expression can come in handy.

### Step 1: Create the computation expression builder

First we need to define a builder class:

` ````
type OptionalBuilder() =
member _.Bind(x : 'a option, f: 'a -> 'b option) = Option.bind f x
member _.Return(x: 'a) = Some x
```

This class has only two members: **Bind** and **Return**. **Bind** is the function that will unwrap an option to it's inner value if it's **Some**, and if it's **None** it will just return **None**. **Return** essentially just wraps a value in **Some**.

The member names used for computation expressions have a well-defined pattern, but due to the nature of higher-kinded types, and the fact that any computation expression may not need all available functionality, they can't currently be modeled by a simple interface.

So a builder class doesn't need to implement an interface, it just has to define the relevant member methods. You can see which methods are available for this purpose with first-class support.

### Step 2: Create an instance

Once we have our builder class, we create an instance of it, and that instance becomes the "keyword" to use in our computation expression:

` ``let optional = OptionalBuilder()`

### Step 3: Rewrite with the optional computation expression

Now we can use our **optional** computation expression to rewrite our code:

` ````
let doSomething (a: string option) (b: int option) (c: float option) =
optional {
let! a' = a
let! b' = b
let! c' = c
return doSomethingWithAll a' b' c'
}
```

Much cleaner! We can now focus on the happy path, and if any of the options are **None**, we'll just return **None** from the whole function.

## Wrapping up: Functional programming with F#

Out of the box, F# computation expressions can be used to support standard functional programming operations that you'd expect from monadic types. There are a lot more things you can do with the built-in computation expression functions, and you can also utilize custom operations to essentially create your own Domain-Specific Languages (DSLs)!

Computation expressions are incredibly powerful, and I've used them when creating a simple SQL query builder, http request builder, and dependency injection container registrations. Try and see what you can build with them!