Functors, Applicatives and Monads
Functors
In haskell, functors are basically type constructors that define the fmap function, i.e.,
they take a type t and wrap them in their own type which is constructed by passing t to
the type constructor.
To figure out more about type constructors, they're basically the same as value
constructors, just for types. So, in haskell (not talking about the extensions), each
type has a kind. For instance, all the types that don't take a type argument have the kind
*. On the other hand, all the types that take one parametric type have the kind * -> *.
Similarly, types like Either have the kind * -> * -> *. You get the gist. This is
basically a second order abstraction over types.
This is in particular, distinct from Rust where there are two base kinds, the type and the lifetime. They follow different semantics.
So basically a type constructor is a functor if you can take a function and use that to map the inner type in the type constructor's concretization to another type wrapped in the same type constructor.
I use the term wrapped very loosely over here, but get the point. It's basically the application of the type constructor on that type.
In terms of signature, it's defined as:
class Functor f where
fmap :: (a -> b) -> f a -> f b
The types make it obvious. You take the function and map it on the included type to yield a new wrapped type. It can also be seen this way: given a function, a functor wraps the input and the output of the function in its type constructor.
The Rules of Functors
A functor over an ID function should yield the same value. This seemingly comes from category theory, where non-bottom types form the category Hask, where functions are endofunctors over the category. I don't know much about what those words mean :cry:.
Regardless, if this doesn't hold, then the type constructor is not a functor.
TL;DR:
fmap id = idholds- The second rule is that two functions composed and then fed to the functor should lead to the two functions fed to the functor and then composed.
Applicatives
Applicatives are somewhat like functors on steroids. In haskell, we don't differentiate
between functions and regular types. In essence, a function is just a value. Applicatives
allow us to wrap those up in the type-constructor as well and then use that to map the
same way fmap did.
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
pure, well, lifts the function/value from the base type to the derived type, and
the fancy operator does the exact same thing as an fmap, except with functions that
are already wrapped by the type-constructor.
One very interesting example of the applicative typeclass is the ((->) r) type,
which means, functions. In a way the partial application of them.
The way to understand see visualize interpret it would be to take the -> as a typeclass
taking two types and returning a function that takes the former and returns the latter.
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
Over here, pure makes intuitive sense. The way to lift a "value" to a function is, well,
a thunk (ideally a thunk that takes (), but beggars can't be choosers. We need the type
to be a generic cuz of type constraint, or the lack thereof, on the type of pure).
The funky operator is, well, intriguing to say the least. The types check out: the first
is an f that's of the type r -> a -> b, and the second argument is of the type r -> a
and the output is of the type r -> b. x in the lambda is of the type r.
But what does it even mean??
Treat r as a shared state type between the functions.
isAlphaNum :: Char -> Bool
isAlphaNum = pure (||) <*> isAlpha <*> isDigit
The same can also be interpreted as a functor (of course, because of the type constraints). In that case
fmapjust behaves as the composition operator. It should be straightforward to see why.
Monads
A Monad is basically representing a value in terms of a context. You might ask how this is different than Applicative that we discussed before. Honest answer, it's not. Except, it allows you to sequence things in a way where the effect of the next computation depends on the previous one.
Think about it, you can't do that with Applicatives. That's because the computations never have the unwrapped value of the previous one. They're totally independent in terms of computations. The pipeline is already built and statically determined. You can add a form of choice by having choices in a wrapper pure function that finally calls one or the other, but yeah, Monad adds that to the structure of the typeclass itself.
More importantly, the point I want to drive home is the fact that a monadic computation has access to the results of the previous computations in the pipeline. Applicative is really just carrying the argument wrapped in a typeclass to a prebaked function, which has the entire pipeline shoved in it.
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
return behaves similar to pure, lifting a value to a context. The other operator is also called
bind, as it binds the result of the previous computation to a new one.
Monads give you a choice
Based on the outcome of the previous computation, you can change the thing that you want to do
in the new computation. This is a very powerful abstraction. The same is clearly not possible
in Applicatives because the entire pipeline is decided statically. In fact, they are arguments
to a function, not even the function itself. You can compose various functions but be prepared
for the branching thrown at your face when you have to make decisions in each function. Monad's
bind lets you deal with it in a more structured fashion.
Choice at failure??
Notice that Monads give you no choice over what happens when a computation fails. That's where
the Alternative typeclass comes in, which provides exactly that. It allows you to choose between
two different computations in case the first one fails. It also expects you to have an empty
computation. This is because it leads to having a notion of an impossible choice, and having the
Alternative behave more like a monoid. (A monoid is nothing but a typeclass for roughly the same
thing, except more of collection in the sense).
Monadic Laws
As with everything else, there are laws surrounding monads.
return a >>= k = k a: Lift a value into a contextual form, and bind it, and the result is the same as calling the function on it directly.m >>= return = m: Take a value in a context, bind it to return, and it'll give the value back. This is basically equivalent to unwrapping it from the context and wrapping it again.m >>= (\x -> k x >>= h) = (m >>= k) >>= h: Associativity of the effectful computations. Basically, as long as the sequence is maintained, the associativity doesn't matter.
