The Monad
typeclass can be defined in terms of return
and (>>=)
. However, if we already have a Functor
instance for some type constructor f
, then this definition is sort of 'more than we need' in that (>>=)
and return
could be used to implement fmap
so we're not making use of the Functor
instance we assumed.
In contrast, defining return
and join
seems like a more 'minimal'/less redundant way to make f
a Monad
. This way, the Functor
constraint is essential because fmap
cannot be written in terms of these operations. (Note join
is not necessarily the only minimal way to go from Functor
to Monad
: I think (>=>)
works as well.)
Similarly, Applicative
can be defined in terms of pure
and (<*>)
, but this definition again does not take advantage of the Functor
constraint since these operations are enough to define fmap
.
However, Applicative f
can also be defined using unit :: f ()
and (>*<) :: f a -> f b -> f (a, b)
. These operations are not enough to define fmap
so I would say in some sense this is a more minimal way to go from Functor
to Applicative
.
Is there a characterization of Monad
as fmap
, unit
, (>*<)
, and some other operator which is minimal in that none of these functions can be derived from the others?
(>>=)
does not work, since it can implement a >*< b = a >>= (\ x -> b >>= \ y -> pure (x, y))
where pure x = fmap (const x) unit
.join
since m >>= k = join (fmap k m)
so (>*<)
can be implemented as above.(>=>)
fails similarly.Monads are not a replacement for applicative functors Instead, every monad is an applicative functor (as well as a functor).
An applicative is a data type that implements the Applicative typeclass. A monad is a data type that implements the Monad typeclass. A Maybe implements all three, so it is a functor, an applicative, and a monad.
As I understand, every monad is a functor but not every functor is a monad. A functor takes a pure function (and a functorial value) whereas a monad takes a Kleisli arrow, i.e. a function that returns a monad (and a monadic value).
In Haskell, an applicative is a parametrized type that we think of as being a container for data of that type plus two methods pure and <*> . Consider a parametrized type f a . The pure method for an applicative of type f has type. pure :: a -> f a. and can be thought of as bringing values into the applicative.
I have something, I think. It's far from elegant, but maybe it's enough to get you unstuck, at least. I started with join :: m (m a) -> ???
and asked "what could it produce that would require (<*>)
to get back to m a
?", which I found a fruitful line of thought that probably has more spoils.
If you introduce a new type T
which can only be constructed inside the monad:
t :: m T
Then you could define a join-like operation which requires such a T
:
joinT :: m (m a) -> m (T -> a)
The only way we can produce the T
we need to get to the sweet, sweet a
inside is by using t
, and then we have to combine that with the result of joinT
somehow. There are two basic operations that can combine two m
s into one: (<*>)
and joinT
-- fmap
is no help. joinT
is not going to work, because we'll just need yet another T
to use its result, so (<*>)
is the only option, meaning that (<*>)
can't be defined in terms of joinT
.
You could roll that all up into an existential, if you prefer.
joinT :: (forall t. m t -> (m (m a) -> m (t -> a)) -> r) -> r
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With