Computation Expressions: an F# feature I wish I had but that will never be implemented in C#
Tags: csharp, fsharp, programming, dotnet
Published: 2023-10-09 16:36:39

Seasoned developers like myself sometimes invariably stumble upon charming paradigms in one language that evokes longing when coding in the other. One spellbinding feature from the F# toolkit is [F# Computation Expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions). While we can dream, it’s a feature unlikely to find a home in C#.

## Introducing a Use Case

To scrutinize what’s at stake, let's dissect an example inspired by my [previous article](https://dev.to/maxarshinov/domain-services-in-ddd-navigating-through-validation-and-dependency-management-2cjn), which highlights two irksome aspects of C#:

```csharp
public async Task<IActionResult> ChangeEmail(int userId, string newEmail)
{
    User existingUser = await _userRepository.GetByEmailAsync(newEmail);
    if (existingUser != null && existingUser.Id != userId)
    {
        return BadRequest();
    }

    User user = await _userRepository.GetByIdAsync(userId);
    newEmail = CanonicalizeEmail(newEmail);
    user.ChangeEmail(newEmail);
    await _userRepository.SaveAsync(user);
    await _notifications.SendAsync(user);
    return Ok();
}
```

### The DRY Challenge

The code snippet below tends to be repetitive. DRY (Don't Repeat Yourself) becomes a hurdle when navigating through the asynchronous verification mechanism in C#.

```csharp
User existingUser = await _userRepository.GetByEmailAsync(newEmail);
if (existingUser != null && existingUser.Id != userId)
{
    return BadRequest();
}
```

### The “async/await” Predicament

The Task keyword dictates an unavoidable cascade of "[async all the way](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#async-all-the-way)" down your call stack.

Though neither of these issues critically undermines code maintainability, they can ruffle the feathers of developers striving for elegant code, especially those involved in code reviews.

## Introducing F# to the Stage

F# provides a splendidly terse and comprehensible alternative. Don't worry if you're not familiar with the syntax as I only want to compare the readability of the C# and the F# versions:

```fsharp
let changeEmail =
    validateRequest
    >> canonicalizeEmail
    >> updateDbFromRequest
    >> sendEmail
    >> returnMessage
```

- As you've probably guessed, `validateRequest`, `canonicalizeEmail`, and the whole `changeEmailWorkflow` are just functions. 
- The composition operator `>>` takes two functions and returns a function.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cgnbjsiaohrldphr7cje.png)

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wxnylvzkimi8me4tzy0z.png)

> Visual aids borrowed from Scott Wlaschin's [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/) illustrate this point wonderfully.

Even those who are unfamiliar with the syntax can read and understand it. In fact, you can even show this code to non-technical people and most likely they will be able to read and understand the snippet. When reformulated in a less idiomatic F# style, an extra layer of verbosity and complexity becomes apparent:

```fsharp
let changeEmailWithLet (request: ChangeEmailCommand) =
    let validated = validateRequest request 
    let canonicalized = canonicalizeEmail validated
    let updated = updateDbFromRequest canonicalized
    let sent = sendEmail updated
    returnMessage sent
```

Despite this, it still maintains arguably better readability than its C# counterpart. Regardless, this transformation is essential for understanding the main concept of this article.

## Unraveling Computation Expressions

F#'s [Computation expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions) furnish developers with a syntax that allows computations to be sequenced and combined in a delightful manner. But let's shift from definition to practice, with a `result` computation as an example:

### Result

The [`Result<'T,'TError>`](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/results) type lets you write error-tolerant code that can be composed.

```fsharp
// The definition of Result in FSharp.Core
type Result<'T,'TError> =
    | Ok of ResultValue:'T
    | Error of ErrorValue:'TError
```

This function employs the `Result<'T,'TError>` type to yield an `Ok` or `Error` return. 

```fsharp
let divide x y = 
    if y = 0.0 then Error "Cannot divide by zero"
    else Ok (x / y)
```

The caller has to match the returning value and check if it's an `Ok` or an `Error` case. This kind of checking might be tedious and messy when we need to chain several division operations like on the code snippet below:

```fsharp
let compute a b c d =
    match divide a b with
    | Error e -> Error e
    | Ok division ->
        match divide c d with
        | Error e -> Error e
        | Ok anotherDivision -> Ok (division + anotherDivision)
```

When chain-linked through various operations, the simplicity and efficacy of computation expressions shine. Instead of nesting `match/with` expressions, we can wrap divisions in the `result {}` context so we can use `let!` and `return`:

```fsharp
let result = ResultBuilder()

let compute a b c d =
    result {
        let! division = divide a b
        let! anotherDivision = divide c d
        return division + anotherDivision
    }
```

The code within the `result {}` context is a computation expression. The programmer is free to define the semantics of the context. In our case we want to continue execution on `Ok` and skip execution on `Error`.

```fsharp
type ResultBuilder() =
    member _.Bind(x, f) =
        match x with
        | Ok v -> f v
        | Error e -> Error e
    member _.Return(x) = Ok x
```

The `Bind` function enables the `let!` keyword. It "unwraps" the `Result<'T, string>` value by chaining the following results of the computation expression. 

- `x` is the result of `divide a b`
- `f` is the remaining part of the computation that depends on unwrapped value of x:

```fsharp
let! anotherDivision = divide c d
return division + anotherDivision
```

The `Return` function enables the `return` keyword so the whole computation still returns `Result<'T, string>`. This is why `Result<'T, string>` is defined as a way to write error-tolerant code that **can be composed**.

## Validation

Once written, the `ResultBuilder` can be applied to any workflow that works with any `Result<'T,'Terror>` type. Let's apply it to the original "Update Email" use-case:

```fsharp
type SimpleResult<'T> = Result<'T, string> // It can be any error type. String is for simpicity reasons only.

let changeEmail request =
    result {
        let! validated = validateRequest request // unwraps Result
        let canonicalized = canonicalizeEmail validated
        let! updated = updateDbFromRequest canonicalized // unwraps Result
        let! sent = sendEmail updated // unwraps Result
        return sent // wraps the result
    } |> returnMessage // SimpleResult<ChangeEmailCommand> -> IActionResult
```

> You might notice that `let canonicalized = canonicalizeEmail validated` is the only line that uses a regular version of `let` instead of `let!`. That's because it returns `ChangeEmailCommand` rather than `Result<ChangeEmailCommand, 'TError>`. There is no need to unwrap the returning value. It wasn't wrapped because the function can't fail. 

Compare it to the previous version. I only slightly updated the last line with the [forward pipe](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/#function-symbols-and-operators) `|>` operator to make it consistent with the computation-expressions-based version of the code:

```fsharp
let changeEmailWithLet request =
    let validated = validateRequest request
    let canonicalized = canonicalizeEmail validated
    let updated = updateDbFromRequest canonicalized
    let sent = sendEmail updated
    sent |> returnMessage
```

It's not very different, right?  The beauty of computation expressions is that they are a generic concept. They are not limited to `Result<'T, 'TError>`. For instance, `Task<'T>` [has built-in support](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/task-expressions) of computation expressions.

## Blending Asynchronicity and Validation

Moreover, it's possible to build a computation expression that supports both error handling and asynchronicity: 

```fsharp
type TaskResult<'T> = Task<SimpleResult<'T>>

type TaskResultBuilder<'T>() =
    member _.Bind(x: SimpleResult<'T>, f: 'T -> TaskResult<'U>) : TaskResult<'U> =
        // ...
    member _.Bind(x: TaskResult<'T>, f: 'T -> TaskResult<'U>) : TaskResult<'U> =
        // ...

    member _.Return(x: 'T) : TaskResult<'T> = 
        // ...

    member _.Return(x: SimpleResult<'T>) : TaskResult<'T> = 
        // ...
```

> You can find the implementation details in Mark Seemann's [Task asynchronous programming as an IO surrogate](https://blog.ploeh.dk/2020/07/27/task-asynchronous-programming-as-an-io-surrogate/).

The code inside remains the same despite all functions are now asynchronous. We are now using the enhanced `taskResult` computation expression that supports the `Task<Resut<'T, 'TError>` type. 

```fsharp
let changeEmail request =
    taskResult {
        let! validated = validateRequest request // unwraps TaskResult
        let canonicalized = canonicalizeEmail validated
        let! updated = updateDbFromRequest canonicalized // unwraps TaskResult
        let! sent = sendEmail updated // unwraps Result
        return sent // wraps TaskResult
    } |> returnMessage // TaskResult<ChangeEmailCommand> -> Task<IActionResult>
```

Note that despite the `let! sent = sendEmail updated // ChangeEmailCommand -> SimpleResult<ChangeEmailCommand>` type definition lacks asynchronicity we can still use `let!` thanks to the overloaded implementation of `Bind` from the `TaskResultBuilder<'T>` definition above.

## Operator Overloading

Last but not least. The code example above is still verbose. All these parameters we had to add to make `let!` work ruin the beauty of the original example. The good news is that F# supports operator overloading! There is even a well-known convention for monadic compositions. It's called "[Kleisli composition](https://fsharpforfunandprofit.com/rop/#relationship-to-the-either-monad-and-kleisli-composition)" or just "fish operator": `>=>`. Let's define the Kleisli composition operator and two additional operators that might be handy:

- **`>=>` :** composes functions that are both asynchronous and can fail.
- **`>>>` :** composes asynchronous functions that can fail and simple functions that can't fail. 
- **`>?>` :** composes asynchronous functions that can fail and synchronous functions that can fail.

```fsharp    
let changeEmailKleisli =
    validateRequest // 'T -> TaskResult<'T>
    >>> canonicalizeEmail // canonicalizeEmail is synchronous and always successful
    >=> updateDbFromRequest // updateDbFromRequest is asynchronous and can fail
    >?> sendEmail // sendEmail is synchronous and can fail
    >> returnMessage // return message just translates the Result type to IActionResult
```

Let's compare it to the original F# example:

```fsharp
let changeEmail =
    validateRequest
    >> canonicalizeEmail
    >> updateDbFromRequest
    >> sendEmail
    >> returnMessage
```

There is indeed some minor difference: the original example only uses `>>` while the latter version leverages more advanced composition types: `>>`, `>=>`, `>>>`, `>?>`. These operators might be confusing at first. However, in the long run, I like that the operators are different. As a code reviewer, I might be interested in what kind of composition. Different operators make it clear that the final workflow consists of functions with different signatures.

## Limitations of the Parameterless Version

Alas, despite being review-friendly, the last variant is not without drawbacks. I can name two major ones.

### Debugging experience

Whichever composition `>>`, `>=>`, `>>>`, `>?>` is used, there is no way to set a breakpoint on a junction side. Thus, to debug the composed function one needs to add breakpoints inside of the bodies of the composed function. This is the price one has to pay for getting rid of `let` keywords and parameters. Arguably, this is a major tradeoff between two maintainability aspects.

### Operators are Stateless

Until now, all our builders had parameterless constructors. However, they don't have to. Should we need to add logging support, only one modification of `Bind` is required. Unlike Scala, there is no straightforward way to inject a dependency into an operator in F# ... Maybe it's a good thing, though.

```fsharp
type TaskResultBuilder<'T>(logger: ILogger<'T>) =
    member _.Bind(x: TaskResult<'T>, f: 'T -> TaskResult<'U>) : TaskResult<'U> =
    // add logging here...
    // it will trigger on every let!

//...

let taskResult = TaskResultBuilder(logger)
```

## Why This Feature Will Never Be Implemented in `C#`

C# follows a completely different design approach: `async/await`, `yield`, `LINQ`, all these quality-of-life features. While F# gives developers powerful but sometimes too generic and not always easy-to-grasp abstractions, C# uses specific and somewhat opinionated (in a good way) tools that do just one thing but do it great. The difference in these two approaches to language design might be as significant as the OOP/Functional-first natures of C# and F# correspondingly.

While there are ways to implement [weird things](https://github.com/yuretz/FreeAwait) by abusing `async/await` keywords or the `SelectMany` method, these are at most amusing exercises rather than production-ready solutions. 

## Conclusion

The elegance and conciseness of F# offer undeniable allure, particularly when contrasted against specific use cases in C#. While F# bestows a straightforward approach to managing asynchronicity and validation, C# developers can only peer into this functional world with a hint of envy.