How do I get the value out of my monad? You don't. You inject the desired behaviour into the monad.

A frequently asked question about monads can be paraphrased as: How do I get the value out of my monad? This seems to particularly come up when the monad in question is Haskell's IO monad, from which you can't extract the value. This is by design, but then beginners are often stumped on how to write the code they have in mind.

You can encounter variations of the question, or at least the underlying conceptual misunderstanding, with other monads. This seems to be particularly prevalent when object-oriented or procedural programmers start working with Maybe or Either. People really want to extract 'the value' from those monads as well, despite the lack of guarantee that there will be a value.

So how do you extract the value from a monad?

The answer isn't use a comonad, although it could be, for a limited set of monads. Rather, the answer is mu.

Unit containers #

Before I attempt to address how to work with monads, I think it's worthwhile to speculate on what misleads people into thinking that it makes sense to even contemplate extracting 'the value' from a monad. After all, you rarely encounter the question: How do I get the value out of my collection?

Various collections form monads, but everyone intuitively understand that there isn't a single value in a collection. Collections could be empty, or contain many elements. Collections could easily be the most ordinary monad. Programmers deal with collections all the time.

Yet, I think that most programmers don't realise that collections form monads. The reason for this could be that mainstream languages rarely makes this relationship explicit. Even C# query syntax, which is nothing but monads in disguise, hides this fact.

What happens, I think, is that when programmers first come across monads, they often encounter one of a few unit containers.

What's a unit container? I admit that the word is one I made up, because I couldn't detect existing terminology on this topic. The idea, though, is that it's a functor guaranteed to contain exactly one value. Since functors are containers, I call such types unit containers. Examples include Identity, Lazy, and asynchronous functors.

You can extract 'the value' from most unit containers (with IO being the notable exception from the rule). Trivially, you can get the item contained in an Identity container:

> Identity<string> x = new Identity<string>("bar");
> x.Item
"bar"

Likewise, you can extract the value from lazy and asynchronous values:

> Lazy<int> x = new Lazy<int>(() => 42);
> x.Value
42

> Task<int> y = Task.Run(() => 1337);
> await y
1337

My theory, then, is that some programmers are introduced to the concept of monads via lazy or asynchronous computations, and that this could establish incorrect mental models.

Semi-containers #

There's another category of monad that we could call semi-containers (again, I'm open to suggestions for a better name). These are data containers that contain either a single value, or no value. In this set of monads, we find Nullable<T>, Maybe, and Either.

Unfortunately, Maybe implementations often come with an API that enables you to ask a Maybe object if it's populated or empty, and a way to extract the value from the Maybe container. This misleads many programmers to write code like this:

Maybe<int> id = // ...
if (id.HasItem)
    return new Customer(id.Item);
else
    throw new DontKnowWhatToDoException();

Granted, in many cases, people do something more reasonable than throwing a useless exception. In a specific context, it may be clear what to do with an empty Maybe object, but there are problems with this Tester-Doer approach:

  • It doesn't compose.
  • There's no systematic technique to apply. You always need to handle empty objects in a context-specific way.
These issues interact in unpleasant ways.

If you throw an exception when the object is empty, you'll likely have to deal with that exception further up in the call stack.

If you return a magic value (like returning -1 when a natural number is expected), you again force all callers to check for that magic number.

If you set a flag that indicates that an object was empty, again, you put the burden on callers to check for the flag.

This leads to defensive coding, which, at best, makes the code unreadable.

Behaviour Injection #

Interestingly, programmers rarely take a Tester-Doer approach to working with collections. Instead, they rely on APIs for collections and arrays.

In C#, LINQ has been around since 2007, and most programmers love it. It's common knowledge that you can use the Select method to, for example, convert an array of numbers to an array of strings:

> new[] { 42, 1337, 2112, 90125 }.Select(i => i.ToString())
string[4] { "42", "1337", "2112", "90125" }

You can do that with all functors, including Maybe:

Maybe<int> id = // ...
Maybe<Customer> c = id.Select(x => new Customer(x));

A previous article offers a slightly more compelling example:

var viewModel = repository.Read(id).Select(r => r.ToViewModel());

Common to all the three above examples is that instead of trying to extract a value from the monad (which makes no sense in the array example), you inject the desired behaviour into the context of the data container. What that eventually brings about depends on the monad in question.

In the array example, the behaviour being injected is that of turning a number into a string. Since this behaviour is injected into a collection, it's applied to every element in the source array.

In the second example, the behaviour being injected is that of turning an integer into a Customer object. Since this behaviour is injected into a Maybe, it's only applied if the source object is populated.

In the third example, the behaviour being injected is that of turning a Reservation domain object into a View Model. Again, this only happens if the original Maybe object is populated.

Composability #

The marvellous quality of a monad is that it's composable. You could, for example, start by attempting to parse a string into a number:

string candidate = // Some string from application boundary
Maybe<int> idm = TryParseInt(candidate);

This code could be defined in a part of your code base that deals with user input. Instead of trying to get 'the value' out of idm, you can pass the entire object to other parts of the code. The next step, defined in a different method, in a different class, perhaps even in a different library, then queries a database to read a Reservation object corresponding to that ID - if the ID is there, that is:

Maybe<Reservation> rm = idm.SelectMany(repository.Read);

The Read method on the repository has this signature:

public Maybe<Reservation> Read(int id)

The Read method returns a Maybe<Reservation> object because you could pass any int to the method, but there may not be a row in the database that corresponds to that number. Had you used Select on idm, the return type would have been Maybe<Maybe<Reservation>>. This is a typical example of a nested functor, so instead, you use SelectMany, which flattens the functor. You can do this because Maybe is a monad.

The result at this stage is a Maybe<Reservation> object. If all goes according to plan, it's populated with a Reservation object from the database. Two things could go wrong at this stage, though:

  1. The candidate string didn't represent a number.
  2. The database didn't contain a row for the parsed ID.
If any of these errors occur, idm is empty.

You can now pass rm to another part of the code base, which then performs this step:

Maybe<ReservationViewModel> vm = rm.Select(r => r.ToViewModel());

Functors and monads are composable (i.e. 'chainable'). This is a fundamental trait of functors; they're (endo)morphisms, which, by definition, are composable. In order to leverage that composability, though, you must retain the monad. If you extract 'the value' from the monad, composability is lost.

For that reason, you're not supposed to 'get the value out of the monad'. Instead, you inject the desired behaviour into the monad in question, so that it stays composable. In the above example, repository.Read and r.ToViewModel() are behaviors injected into the Maybe monad.

Summary #

When we learn something new, there's always a phase where we struggle to understand a new concept. Sometimes, we may, inadvertently, erect a tentative, but misleading mental model of a concept. It seems to me that this happens to many people while they're grappling with the concept of functors and monads.

One common mental wrong turn that many people seem to take is to try to 'get the value out of the monad'. This seems to be particularly common with IO in Haskell, where the issue is a frequently asked question.

I've also reviewed enough F# code to have noticed that people often take the imperative, Tester-Doer road to 'a option. That's the reason this article uses a majority of its space on various Maybe examples.

In a future article, I'll show a more complete and compelling example of behaviour injection.


Comments

Sean Donohue

Hi Mark, was very interested in your post as I do try and use Option Monads in my code, and I think I understand the point you are making about not thinking of an optional value as something that is composable. However, I recently had a couple of situations where I reluctantly had to check the value, would really appreciate any thoughts you may have?

The first example was where I have a UI and the user may specify a Latitude and a Longitude. The user may not yet have specified both values, so each is held as an Option. We then need to calculate the rhumb bearing to a fixed location, so I wrote:

if(latitude.HasValue && longitude.HasValue)
Bearing = CalculateRhumbBearing(latitude.Value, longitude.Value, fixedLatitude, fixedLongitude).ToOptionMonad();
else
Bearing = OptionMonad.None;

Having read your article, I realise I could change this to a Select statement on latitude, but that lambda would still need to check longitude.HasValue. Should I combine the two options somehow before doing a single Select?

The second example again relates to a UI where the user can enter values in a grid, or leave a row blank. I would like to calculate the mean, standard deviation and root mean square of the values, and normally all these functions would have the signature: double Mean(ICollection values)

If I keep this then I need a function like

foreach(var item in values)
{
  if(item.HasValue)
  {
    yield return item.Value;
  }
}

Or some equivalent Where/Select combination. Can you advise me please, how you recommend transforming an IEnumerable> to an enumerable? Or should I write a signature overload double Mean(ICollection> possibleValues) and ditto for SD and RMS?

Thanks, Sean

2018-02-05 11:30 UTC

Sean, thank you for writing. The first example you give is quite common, and is easily addressed with using the applicative or monadic capabilities of Maybe. Often, in a language like C#, it's easiest to use monadic bind (in C# called SelectMany):

Bearing = latitude
    .SelectMany(lat => longitude
        .Select(lon =>
            CalculateRhumbBearing(lat, lon, fixedLatitude, fixedLongitude)));

If you find code like that disagreeable, you can also write it with query syntax:

Bearing =
    from lat in latitude
    from lon in longitude
    select CalculateRhumbBearing(lat, lon, fixedLatitude, fixedLongitude);

Here, Bearing is a Maybe value. As you can see, in neither of the above alternatives is it necessary to check and extract the values. Bearing will be populated when both latitude and longitude are populated, and empty otherwise.

Regarding the other question, being able to filter out empty values from a collection is a standard operation in both F# and Haskell. In C#, you can write it like this:

public static IEnumerable<T> Choose<T>(this IEnumerable<IMaybe<T>> source)
{
    return source.SelectMany(m => m.Match(new T[0], x => new[] { x }));
}

This example is based on the Church-encoded Maybe, which is currently my favourite implementation. I decided to call the method Choose, as this is also the name it has in F#. In Haskell, this function is called catMaybes.

2019-02-05 16:25 UTC
Achim Stuy

Hi Mark, did you ever think about publishing a Library containing all these types missing in .net Framework like Either? Or can you recommend an existing library?

2019-02-07 07:59 UTC

Achim, thank you for writing. The thought has crossed my mind, but my position on this question seems to be changing.

Had you asked me one or two years ago, I'd have answered that I hadn't seriously considered doing that, and that I saw little reason to do so. There is, as far as I can tell, plenty of such libraries out there, although I can't recommend any in particular. This seems to be something that many people create as part of a learning exercise. It seems to be a rite of passage for many people, similarly to developing a Dependency Injection container, or an ORM.

Besides, a reusable library would mean another dependency that your code would have to take on.

These days, however, I'm beginning to reconsider my position. It seems that no such library is emerging as dominant, and some of the types involved (particularly Maybe) would really be useful.

Ideally, these types ought be in the .NET Base Class Library, but perhaps a second-best alternative would be to put them in a commonly-used shared library.

2019-02-07 11:15 UTC
Ralph Hendriks

Hi Mark, thank you for the interesting article series.

Can you maybe provide guidance of how asynchronous operations can become part of a chain of operations? How would the 'functor flattening' be combined with the built Task/Task types? Extending your example, how would you go about if we would like to enrich the reservation retrieved from repository with that day's special, which happens to be async:

Task EnrichWithSpecialOfTheDayAsync(Reservation reservation)

I tried with your Church encoded Maybe implementation, but I got stuck with the Task wrapping/unwrapping/awaiting.

2019-02-07 15:06 UTC

Ralph, thank you for writing. Please see if my new article Asynchronous Injection answers your question.

2019-02-11 7:56 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 04 February 2019 07:45:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 04 February 2019 07:45:00 UTC