Haskell's Maybe and Either types

Let's Read: Haskell Programming from First Principles, pt XII
The Haskell logo over the words 'Maybe && Either: An introduction'

Haskell logo fill by Paweł Czerwiński on Unsplash

If you don't have exceptions and you don't have null: How do you handle errors and invalid inputs? Based on your background, this can either be trivial or mind-bending. Coming into the Haskell (actually: Elm and Rust) world from C#, JavaScript, and Python, it definitely wasn't obvious to me at first.

Luckily, the Maybe and Either types are there for you, and they are fairly easy to get started with. In this post, I want to give a quick introduction to both of these data types and how they're used.

To any returning readers: This post is based chapter 12 of /Haskell Programming
from First Principles/, which deals with basic error handling in Haskell, using
the ~Maybe~ and ~Either~ types. The chapter doesn't actually introduce much else
of interest, so I wanted to approach this entry slightly differently.

What we will (and won't) be covering

This post is intended to be a brief introduction to the Maybe and Either data types in Haskell; suitable for someone who has no previous experience with these. Some understanding of Haskell syntax would be beneficial, but is not required. The post should give the reader a basic understanding of what Maybe and Either are and how they can be used for modeling data and responses.

This post is not intended to be a thorough examination of these types. It will not discuss anything related to typeclasses, mapping, or binding. There will also not be any extensive code samples. As such, please note that there is a lot more to these data types than we will look at here, but that most of it is out of scope for this post.

For further reading, please consult the section at the end of the article.

Maybe

Maybe represents the potential absence of a value. It is used when some data can be in one of two states: defined/present, or undefined/absent. Because Maybe represents a value that may or may not be present, we also need to specify the type of the potentially contained item: Maybe String, Maybe Integer, or Maybe a for instance.

In many ways, Maybe is similar to null in C#, JavaScript, etc., None in Python, and related concepts (like nil) in a lot of other languages. However, in these other languages, the potential lack of a value is (almost) always implicit, meaning the programmer can never be sure whether the value is there or not. In Haskell, this is always explicit. The Maybe type is the same as the Option type in languages like Rust and F#.

The data declaration looks a little something like this:

data Maybe a = Nothing | Just a

As such, Maybe has two data constructors, Nothing and Just, where the latter takes a value to wrap.

Basic usage

While there's any number of ways you can use the Maybe type in your programs, I find the two most obvious ways to be

  1. to model data where some parts are optional
  2. to return something from a function if the input is invalid

Being able to indicate that a value may or may not be present is a simple, but very powerful tool of modeling data. Imagine you've created a dating app for Haskellers---let's call it Hinder---where you want to display a list of users. The list should display only their profile picture and their name, but there's no requirement to have set a profile picture to be listed, so we'll need to account for that. A simplistic way to model that would be by using a list of records like this:

data User = User
  { name :: String
  , picture :: Maybe String
  }

Where the picture value is Nothing if there is no profile picture set. If they have a profile picture, it's Just <picture id>.

The other way to use Maybe is as the return value of a function. As much as possible, you should use the type system to constrain the set of allowed arguments to a function, but that'll only get you so far. The built-in div function throws an exception if you pass 0 as the second argument, so let's fix that by using Maybe. If the divisor is 0, return Nothing. Otherwise, return Just <result>.

safeDiv :: Integral a => a -> a -> Maybe a
safeDiv _ 0 = Nothing
safeDiv x y = Just $ x `div` y

Either

The Either type is similar to the Maybe type, but comes with some added functionality. It's also used to model things that can fail, but it provides a way to report what went wrong, not just that something failed.

Either represents a piece of data that can be one of two things. That is, it can be either 'this' or 'that', whatever 'this' and 'that' may be. Either is parameterized by two types (so 'this' and 'that' can be of different types), meaning you'll usually see Either a b or something like Either String Int.

Where Maybe has a very easy analog in the concept of a null value, Either doesn't enjoy the same luxury. However, it is conceptually equivalent to the Result type in Rust and F#, even if the order of the arguments are flipped.

The data declaration (simplified) looks like this:

data Either a b = Left a | Right b

Either has two data constructors, Left and Right, each of which take a single value. By convention, the Left value is used for when something goes wrong, and the Right value for when something goes, well, right.

Basic usage

Either is usually used for error handling and validation, so it's ideal as a return type of functions that can 'fail' or that are otherwise not able to create a valid result based on the arguments given.

Let's return to our safeDiv function from before. This time, however, we'll return an error message instead of Nothing:

safeDiv :: Integral a => a -> a -> Either String a
safeDiv _ 0 = Left "You cannot divide by zero."
safeDiv x y = Right $ x `div` y

The error may be obvious in this case, but for more complex functions (or chains of them), it gets ever more useful.

For a slightly more advanced example, imagine you're creating a Person record. In this case, a Person has a name of type String and an Age of type Integer. But with these constraints, we can easily pass in invalid values, so we'll have to validate that the name isn't an empty string and that the age is non-negative:

data PersonError = InvalidName | InvalidAge

data Person = Person
  { name :: String
  , age :: Integer
  }

makePerson :: String -> Integer -> Either PersonError Person
makePerson name age = undefined -- exercise for the reader ;)

The actual implementation of the validation isn't important in this case, so I've left that as an exercise for the reader. Oh, and what if there are multiple errors? Well, we can deal with that too, but that's not for now.

Further reading

If you have found the article useful and would like to learn more about Maybe, Either, and error handling, here's a little list of useful resources:

The Haskell Wiki
The Haskell wiki is usually one of the first places I

check when I want more information on anything Haskell. They have an article on Maybe as well as one on error handling, which has sections on both ~Maybe~ and ~Either~.

Learn You a Haskell is a pretty good introduction to Haskell that's available in its entirety from the website. The content in the Fistful of Monads chapter is a bit more advanced and might require reading some of the previous chapters, but both the chapter and the book could be well worth a look.

While I've not used The School of Haskell as a resource myself, I did find the error handling chapter to be well written. Oh, and it's written by Bartosz Milewski so you can expect some interesting insights.



Thomas Heartman is a developer, writer, speaker, and one of those odd people who enjoy lifting heavy things and putting them back down again. Preferably with others. Doing his best to gain and share as much knowledge as possible.