Practical Monads with Raku and Monad::Result
Tags: raku, monad
Published: 2023-03-12 16:21:08

[Monads](https://en.wikipedia.org/wiki/Monad_(functional_programming)) are a great tool in every programming language, they provide a simple way to hide side-effects in your code, and in languages that allow even the slightest amount of pattern matching, they enable "Railroad Oriented" programming.

Thankfully, Raku has `given` which enables us to perform simple pattern matching, and with it's dependent-type system we can easily define our own Monads. I wrote [Monad::Result](https://github.com/rawleyfowler/Monad-Result) a month or two ago, and I have used it in almost all of my Raku projects since.

Simply install `Monad::Result` with zef:

```
zef install Monad-Result
```

`Monad::Result` is an extremely simple package that is designed to allow programmers to easily hide exceptions within a container, forcing the caller to evaluate both possible scenarios of the call. This is particularly useful when dealing with IO, Network calls, Databases, Asynchronous callbacks, etc. Basically anything with a well-defined side-effect. If you don't know what a side-effect is, it's basically the "sad-pass" of an operation. So in the case of an HTTP request, it may fail due to a number of reasons, it's failure would be it's side-effect. What's nice about using a Monad to represent this, is we can hide all the ugliness behind an easily consumed interface, whereas with exceptions it becomes quite cumbersome.

## Abstracting away exceptions
The most common way I find myself using `Monad::Result` is with exceptions. Since I can't specify the exceptions in the type-signature, or return type of a function, I find they often go uncaught and lead to some gross bugs. But, with `Monad::Result` as the return type, the developer calling the code can infer "hey, this code might fail".

Here is a simple wrapper around `DBIish` I typically use to hide exceptions from the caller:

```perl6
use DBIish;
use Monad::Result :subs;

my $db = DBIish.connect('SQLite', :database<foo.db>);

sub exec-sql(Str:D $sql, *@args --> Monad::Result:D) is export {
    CATCH {
        default {
            return error($_);
        }
    }

    my $stmt = db.prepare($sql); 
    ok($stmt.execute(|@args).allrows(:array-of-hash));
}
```

Let's break down what's going on inside the `exec-sql` sub-routine.

Since DBIish will fire an exception if something goes wrong, we catch it in the `CATCH`, it will catch any exception and return it as a `Monad::Result{+Monad::Result::Error}` basically a result of error, and obviously, if it succeeds, then the sub-routing will return a `Monad::Result{+Monad::Result::Ok}`, meaning it succeeded.

So if we wanted to use our new `exec-sql` sub-routing downstream we'll know explicitly that this call can, and will fail. So, we can do some pattern-matching with `given` to make sure our code is super safe!

Here is how you typically see a `Monad::Result` consumed:

```perl6
given exec-sql('SELECT * FROM users') {
    when Monad::Result::Ok:D {
        my @users = $_.value; # safe to do so because we know we're Ok!
        say "Got all users! @users";
    }
    when Monad::Result::Error:D {
        my $exception = $_.error;
        warn "Failed to get all users, oh no! Had this error $exception";
    }
}
```

We simply match against the two possibilities for a `Monad::Result`, and our code remains completely safe.

## Bind and Map
Now you're probably wondering "How the heck is this practical, what if I have to combine a hundred side-effect calls? This will become a mess!" And you'd be right, especially if Raku was a [bad language](https://go.dev/learn/) that didn't allow user defined operators. (Just joking Go isn't bad, but Raku is simply better :^). Because of this, we can use the `>>=:` (Bind) and `>>=?` (Map) operators respectfully to manage our Monad.

These operators are super simple, all they do is take a `Monad::Result` on the left and a `Callable` on the right. Then, if the `Monad::Result` on the left is Ok, it will call the `Callable` with underlying value of the result. If the result isn't Ok, we just pass the error along. This allows for beautifully chained side-effects without the possibility of an exception blowing up on you (assuming you only call code that can't cause an exception). If you want to use another exception causing routing within a Bind or Map operation you'll have to write a wrapper that returns a `Monad::Result` instead.

```perl6
exec-sql('SELECT * FROM users') >>=: { say @^a; ok(@^a.map(*.<name>)) };
```

This code will select all users from our database, if it succeeds we'll print to the console all of the users, and map the list to just their name.

We could also use the `>>=?` (Map) operation to avoid having to wrap our return with `ok`.

```perl6
exec-sql('SELECT * FROM users') >>=? { say @^a; @^a.map(*.<name>) };
```

Now, if you're not really a Functional Programmer, these operators might be a little scary. Instead, if you wish, you can use the OO versions like so:

```perl6
exec-sql('SELECT * FROM users').bind({ say @^a; ok(@^a.map(*.<name>)) });
```

and

```perl6
exec-sql('SELECT * FROM users').map({ say @^a; @^a.map(*.<name>) });
```

Personally I like to use the OO version if I have to Bind or Map more than 3 times in a row. 

## Monads in the wild
Here are some snippets of some production code that uses `Monad::Result`

#### Find-one SQL

```perl6
sub find-one($query, *@args) {
    exec-sql($query, |@args) >>=: -> @rows { @rows.elems == 1 ?? ok(@rows[0]) !! error(@rows) };
}
```

This is a simple wrapper around the earlier `exec-sql` routine that binds to it's result and if it isn't exactly one element it will return an Error result.

#### HTTP chaining

**Note**: This code isn't in production yet, but it's a part of a large project I'm working on :)

Another cool use for `Monad::Result` is for HTTP requests, sometimes we need to chain them together, here is a simple example of chaining 3 HTTP requests that rely on each-other:

```perl6
auth-client.get-user-roles($id)
           .bind({ 'ADMIN' (elem) @^a ?? user-client.get-user($id) !! error('User is not an admin') })
           .bind({ dashboard-client.find-dashboard-by-user-id(@^a<id>) });
```

In this example, `auth-client`, `user-client`, and `dashboard-client`, are all wrappers around `Cro::HTTP::Client` that perform HTTP requests to different services. All of the end-points are abstracted by a function and return a `Result::Monad`, allowing the developers consuming them to simply chain them.

## Conclusion

`Monad::Result` allows us to very concisely and elegantly deal with real-world side-effects. You can use them to simply chain side-effect reliant code without having to write a huge `CATCH` block, and they also give the developer consuming our code the chance to realize that the code may return an Error.

[Monad::Result](https://github.com/rawleyfowler/Monad-Result) is not the only monad in the Raku ecosystem, there is also [Definitely](https://github.com/masukomi/Definitely) by Kay Rhodes (aka Masukomi) which provides a very nice "optional" type, that can fill a similar role to `Monad::Result`.

Thanks for reading, Raku Rocks!