Monads, Applicatives, and Functors
The Relationships
The quick way to look at the relation between these three objects is:
Functor -> Applicative -> Monad — The lower ones build to make the higher ones, acting on complex data types, where we want to perform some action or evaluation on the internal values of a the complex data type.
Applied to the contents of "wrapped" types, Functors change values, Applicatives change functionality, and Monads change context. Maxim
-- Abridged :info results:
-- Functor
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- Applicative
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- Monad
class Applicative f => Monad f where
(>>=) :: f a -> (a -> f b) -> f b
<$>is the infix version offmap. This makes Functor match up with<*>and>>=in examples later.pureis the more generalized version ofreturn. For this article, the two are equivalent.
Maybe Some Examples
Maybe is all three of the major above types:
-- Functor example => Operator applied to a single wrapped type
λ> (*2) <$> Just 3 -- Apply *2 to content of Just 3
Just 6 -- Retain result in Maybe type
--λ (*) <$> Just 3 -- Fails; missing an second value to multiply
-- "??? * 3" doesn't make sense
-- Applicative example => Operator applied between two wrapped types
λ> (*) <$> Just 4 <*> Just 3 -- Map (*) over 4 and apply to 3
Just 12 -- Retain result in Maybe type
--λ (*2) <$> Just 2 <*> Just 3 -- Fails; extra operand unnecessary
-- "4 *2 3" doesn't make sense.
λ> Just (+3) <*> Just 4 -- Another example
Just 7
-- Monadic example => Operator applied through wrapped type contexts
-- First, we need context:
stringTooLong :: String -> Int -> Maybe Int
stringTooLong s n
| n > length s = Just (n + 1) -- if n is larger than string length, increment n
| otherwise = Nothing
λ> return 5 >>= stringTooLong "foo" >>= stringTooLong "bar" >>= stringTooLong "fooBar"
Just 8 -- ^^^ Just 6 ^^^ Just 7 ^^^ Just 8
λ> return 3 >>= stringTooLong "foo" >>= stringTooLong "bar" >>= stringTooLong "fooBar"
Nothing -- ^^^ Just 4 ^^^ Just 5 ^^^ 5 < 6, Nothing
-- Regarding returns: `return 5` evaluates to `Just 5` to get the context started;
-- this is done by inference since the following >>= expects a Maybe.
The key is remember what the >>= operator does: (>>=) :: f a -> (a -> f b) -> f b. It takes a functor, "extracts" the value, then convert it to another type of functor. In the above example, the result types are all the same, but they could be different:
smallIntToString :: Int -> Maybe String
smallIntToString n
| n <= 9 = Just (show n) -- only "valid" if n is small
| otherwise = Nothing
λ> return 5 >>= stringTooLong "foo" >>= smallIntToString
Just "6" -- ^^^ Just 6 ^^^ 7 <= 9, so show String, "6"
λ> return 9 >>= stringTooLong "foo" >>= smallIntToString
Nothing -- ^^^ Just 10 ^^^ 10 > 9, so Nothing
Recall that stringTooLong of a string is a partial function. This means >>== can extract the resulting number and pass it to smallIntToString.
λ> :t stringTooLong "foo"
stringTooLong "foo" :: Int -> Maybe Int
It's worth talking about the "strange" (to a procedural programmer) syntax of the first applicative example. It's more obvious if we convert from Maybe context to a List context:
-- instead of:
(*) <$> Just 4 <*> Just 3
--λ Just 12
-- consider:
(*) <$> [4] <*> [3]
--λ [12]
Both [] and Just contain values that we want to get it, then apply some function to. Expressing by breaking the first example down:
λ> (*) <$> Just 4 <*> Just 3
Just 12
λ> applyMultBy4 = (*) <$> Just 4
λ> :t applyMultBy4
applyMultBy4 :: Num a => Maybe (a -> a) -- This is a wrapped `(*) :: a -> a -> a`
λ> applyMultBy4 <*> Just 3
Just 12
Maybe and Either and other similar wrapper types are like Lists in this regard. You can map over them, and in this particular example, what you're mapping is one or more partial functions (via <$>) that can then be applied to a new wrapped type (via <*>).
The takeaway for <*> is that as a subclass of Functor, it makes use of fmap itself when you define it. <*> essentially "cascades" the fmap over the subsequent sequence of functions, which is why you often see a sequence of applicative functions start like this:
(++) <$> foo <*> bar <*> baz -- assume foo, bar, and baz are all wrapped types.
The (++) is applied to the foo, and then that wrapped result is fmaped over bar and baz.
Caution! Whether or not <*> can be chained like this depends on the types and function.
More on Monads
Monads are prisons for side-effects ~~WhatTheFunctional
Requirements of a Monad
Monads have two, optionally three, parts:
- Return: The
returnfunction, which takes a thing and wraps it in a monadic context:return :: a -> m a. - Bind: The binding infix operator,
>>=, which takes a monad and a function that converts the incoming monad contents into another monad context:(>>=) :: m a -> (a -> m b) -> m b. - Then: This auxiliary infix function,
>>, takes a left and right monad and then yields the context of the right monad:m a -> m b -> m b. It works like so:m >> k = m >>= \_ -> k.
Notes
- A monadic context means that the a value is "brought into" the monad.
Maybetakes the form of:Maybe a = Nothing | Just a, soreturn a = Just a; thusais now part of the Maybe's context. - The bind operator essentially says, given a monad and function, take the value of the monad and apply it to whatever context is appropriate, resulting in another context.
- Recall that a
datatype and its parts are all categorically the same. Thus inMaybe a = Nothing | Just a,Nothing, andJustare bohMaybe atypes, and are constructors. - In most examples,
m aandm bare monads of different contexts. That is, we're looking at two different monads applied to whatever types are appropriate:m aandk b(and then shorten those to justmandkfor brevity's sake).
The Monad Laws Laws
- Right Identity:
return a >>= f===f a. Return creates a monad on valueaand when bound to a function, that function will be applied toa. - Left Identity:
m >>= return===m. Given a monad contextm, when bound to a return, will result in the same monad context. - Associativity:
(m >>= f) >>= g===m >>= (\x -> f x >>= g). A monad contextmthat is first bound to a functionfand then bound to another functiongis the as ifmwere bound to a lambda applied tofwhich is bound directly tog.