The Maybe Monad

Maybe is predefined and built in Haskell. Maybe is a type constructor whose definition is built-in as:

data Maybe a = Nothing | Just a

Its meaning as a type constructor is that Maybe accommodates cases where there might be no value. Indeed, in many cases we may have a value or our value may be null. In other languages we may have to have two different types for our value, in Haskell we only need Maybe. Maybe can be Nothing or Just our value. The words “Nothing” and “Just” have no meaning in Haskell. “Nothing” does not mean “null” in Haskell. Again, “Nothing” and “Just” have no meaning in Haskell. They are just words that define different values for the Maybe type constructor. It is how we assign these values and how we handle them in our code that gives them “meaning”.

So, we could have as easily created the following type:

data Maybe a = Nothing1 | Just1 a

or the following type:

data Maybe a = Invalid | Precisely a

or the following type:

data Maybe a = Nonexistent | Ditto a

and Maybe would have exactly the same functionality.

It is worth noting that Nothing precedes Just, because the creators of Maybe wanted Nothing to always be smaller when compared to Just with any value. Of course, this works by also adding the deriving (Show, Ord, Eq) expression at the end of Maybe’s declaration. We will see examples of this issue when I will provide a program that proves Maybe’s behavior.

Maybe is a type constructor, but Haskell has built-in code that elevates it to a Functor, an Applicative, and a Monad. I will now present you with the built-in code for Maybe, in order to understand how Maybe has been made to behave as a Functor, an Applicative, and a Monad. It is really easy to see:


data Maybe a = Nothing
             | Just a

class Functor f where
   fmap :: (a -> b) -> f a -> f b

instance Functor Maybe where
   fmap f (Just x) = Just (f x)
   fmap _ Nothing  = Nothing

class (Functor f) => Applicative f where
   pure  :: a -> f a
   (<*>) :: f (a -> b) -> f a -> f b

instance Applicative Maybe where
   pure = Just
   (Just f) <*> (Just x) = Just (f x)
   _        <*> _        = Nothing

class Monad m where
   return :: a -> m a
   (>>=)  :: m a -> (a -> m b) -> m b
   (>>)   :: m a -> m b -> m b
   fail   :: String -> m a

instance Monad Maybe where
   return x = Just x
   Just x >>= f  = f x
   Nothing >>= f = Nothing
   x >> y = x >>= \_ -> y
   fail _ = Nothing

But the following code is general for Functors, Applicatives and Monads:

class Functor f where
   fmap :: (a -> b) -> f a -> f b

class (Functor f) => Applicative f where
   pure  :: a -> f a
   (<*>) :: f (a -> b) -> f a -> f b

class Monad m where
   return :: a -> m a
   (>>=)  :: m a -> (a -> m b) -> m b
   (>>)   :: m a -> m b -> m b
   fail   :: String -> m a

Thus, the code that only applies to Maybe is:


data Maybe a = Nothing
             | Just a

instance Functor Maybe where
   fmap f (Just x) = Just (f x)
   fmap _ Nothing  = Nothing

instance Applicative Maybe where
   pure = Just
   (Just f) <*> (Just x) = Just (f x)
   _        <*> _        = Nothing

instance Monad Maybe where
   return x = Just x
   Just x >>= f  = f x
   Nothing >>= f = Nothing
   x >> y = x >>= \_ -> y
   fail _ = Nothing

The last two lines in the previous program define the >> and fail functions that are optional to define. Thus the only code that is needed to elevate Maybe to a Monad is:


instance Monad Maybe where
   return x = Just x
   Just x >>= f  = f x
   Nothing >>= f = Nothing

The following program proves that Maybe behaves as a type constructor, a Functor, an Applicative and a Monad:


module Main where

import Control.Applicative

main :: IO ()
main  =
   do
      putStrLn "Program begins."

      putStrLn "Tests that prove that Maybe behaves as a type constructor."

      print (Just 10)
      print (Just 35 > Just 12)
      print (Nothing < Just (-12))
      print (Nothing < Just "test")

      putStrLn "Tests that prove that Maybe behaves as a Functor."

      print (fmap length (Just [1,2,3,4]))
      print (fmap length Nothing)

      putStrLn "Tests that prove that Maybe behaves as an Applicative."

      print ((Just length) <*> (Just [1,2,3,4,5]))
      print ((Just length) <*> Nothing)

      putStrLn "Tests that prove that Maybe behaves as a Monad."

      print (Just [1,2,3,4,5,6] >>= (\xs -> Just (length xs)))
      print (Nothing >>= (\xs -> Just (length xs)))

      putStrLn "Program ends."

The output of the previous program is:


Program begins.
Tests that prove that Maybe behaves as a type constructor.
Just 10
True
True
True
Tests that prove that Maybe behaves as a Functor.
Just 4
Nothing
Tests that prove that Maybe behaves as an Applicative.
Just 5
Nothing
Tests that prove that Maybe behaves as a Monad.
Just 6
Nothing
Program ends.

In the previous program we did not need to include the built-in code that makes Maybe behave as a type constructor, a Functor, an Applicative and a Monad, exactly because this code is already built-in.

Since Maybe is already predefined, we do not need to create it. But in order to demonstrate Haskell’s built-in code in a program, let the following program define our own Maybe type constructor: let us call it Maybe1. Here we go:


module Main where

import Control.Applicative

data Maybe1 a = Nothing1
              | Just1 a
              deriving (Show, Ord, Eq)

instance Functor Maybe1 where
   fmap f (Just1 x) = Just1 (f x)
   fmap _ Nothing1  = Nothing1

instance Applicative Maybe1 where
   pure = Just1
   (Just1 f) <*> (Just1 x) = Just1 (f x)
   _        <*> _        = Nothing1

instance Monad Maybe1 where
   return x = Just1 x
   Just1 x >>= f  = f x
   Nothing1 >>= f = Nothing1
   x >> y = x >>= \_ -> y
   fail _ = Nothing1

main :: IO ()
main  =
   do
      putStrLn "Program begins."

      putStrLn "Tests that prove that Maybe1 behaves as a type constructor."

      print (Just1 10)
      print (Just1 35 > Just1 12)
      print (Nothing1 < Just1 (-12))
      print (Nothing1 < Just1 "test")

      putStrLn "Tests that prove that Maybe1 behaves as a Functor."

      print (fmap length (Just1 [1,2,3,4]))
      print (fmap length Nothing1)

      putStrLn "Tests that prove that Maybe1 behaves as an Applicative."

      print ((Just1 length) <*> (Just1 [1,2,3,4,5]))
      print ((Just1 length) <*> Nothing1)

      putStrLn "Tests that prove that Maybe1 behaves as a Monad."

      print (Just1 [1,2,3,4,5,6] >>= (\xs -> Just1 (length xs)))
      print (Nothing1 >>= (\xs -> Just1 (length xs)))

      putStrLn "Program ends."

The previous program produces the following output:


Program begins.
Tests that prove that Maybe1 behaves as a type constructor.
Just1 10
True
True
True
Tests that prove that Maybe1 behaves as a Functor.
Just1 4
Nothing1
Tests that prove that Maybe1 behaves as an Applicative.
Just1 5
Nothing1
Tests that prove that Maybe1 behaves as a Monad.
Just1 6
Nothing1
Program ends.

Update – Monday, March 10, 2014:

A great question was posed to me: Haskell implements the Maybe Monad as

Just x >>= f  = f x

But shouldn’t Haskell have implemented it as

Just x >>= f  = Just (f x)

instead? This way, the output of >>= will be of type m b, as is supposed to be according to the type signature of >>=, which is

(>>=)  :: m a -> (a -> m b) -> m b.

Great question.

The answer is that Haskell is correct. This is because f represents a function with type signature a -> m b, thus the output of >>= will certainly be of the elevated type m b. So, when we see the symbol f in the definition (implementation) of >>=

Just x >>= f  = f x

we must realize that f is not of type a -> b but of type a -> m b, just as the declaration of >>= demands:

(>>=)  :: m a -> (a -> m b) -> m b.

The function f in the definition

Just x >>= f  = f x

is the function (a -> m b) in the declaration

(>>=)  :: m a -> (a -> m b) -> m b.

So x is of type a and the application of function f on x, denoted as f x, will produce output of type m b.

You see, it easy to get confused. This is because of the fact that when we provide the definition for fmap, f denotes a function with type a -> b. But when we provide the definition for >>=, f denotes a function with type a -> m b. So, please be careful as to what each argument means in each case.

Advertisements

About Dimitrios Kalemis

I am a systems engineer specializing in Microsoft products and technologies. I am also an author. Please visit my blog to see the blog posts I have written, the books I have written and the applications I have created. I definitely recommend my blog posts under the category "Management", all my books and all my applications. I believe that you will find them interesting and useful. I am in the process of writing more blog posts and books, so please visit my blog from time to time to see what I come up with next. I am also active on other sites; links to those you can find in the "About me" page of my blog.
This entry was posted in Development. Bookmark the permalink.