Since the Functor-Applicative-Monad Proposal, Monads are a subclass of Applicative which in turn is a subclass of Functor. Mathematically speaking, this seems to be a sensible choice and I do not have any problems with that.
However, what is irritating me is that one is required to write down the Functor and Applicative instances even if the corresponding laws for fmap
and pure
and <*>
are fixed by the monad laws anyways. Indeed, in the above linked proposal, they write themselves: "You can simply derive these instances from the Monad by adding the following code":
import Control.Applicative (Applicative(..))
import Control.Monad (liftM, ap)
instance Functor m where
fmap = liftM
instance Applicative m where
pure = {- move the definition of `return` from the `Monad` instance here -}
(<*>) = ap
and in most tutorials, you will simply see the equality pure=return
and people then go on to define return
in the Monad instance as before. Therefore, effectively, 10 lines of boilerplate code were added for everyone who simply wants to define a Monad instance.
In mathematics, one usually also does not operate in this way. For example, when defining the composition laws for a certain ring, the reader is usually not explicitly reminded again that rings are special instances of abelian groups under addition which in turn are special instances of groups. This is clear by definition anyways.
Thus my question is: Even though it is fine that Monads are a subclass of Applicative which in turn is a subclass of Functor, couldn't this just "happen in the background" in the sense that the compiler inserts the boilerplate code instead of the programmer?
CodePudding user response:
The advice from the documentation is for legacy types that used to have a Monad
instance defined but no Applicative
one. Then defining those instances in terms of the existing Monad
one is a quick and reliable fix to get the package to compile again.
However, this is not how it should go for new code, since Monad
is actually the more advanced class. The recommended way is to
Derive the
Functor
instance. There is always only one possible way this can be done, and the compiler can do it for you.{-# LANGUAGE DeriveFunctor #-} data YourMonadType a = ... deriving (Functor)
Define
Applicative
. This is not quite automatic, and sometimes<*>
can be a bit counterintuitive, but you'll get the hang of it and a direct implementation can actually be more efficient than(<*>) = ap
.instance Applicative YourMonadType where pure = {- direct definition here -} q <*> r = {- direct definition here -}
Define
Monad
. This won't requirereturn
at all (because it's default= pure
), just>>=
.instance Monad YourMonadType where q >>= f = {- direct definition here -}
Side note: arguably the method of
Monad
should actually bejoin
instead of>>=
. Whilstreturn
and>>=
give rise to all theFunctor
andApplicative
methods,join
is an “orthogonal feature” to them.
In modern GHC it is also possible to obtain a derive-instance for Applicative
by using the via
strategy on the WrappedMonad
newtype:
{-# LANGUAGE DerivingVia, DeriveFunctor #-}
import Control.Applicative
data YourMonadType a = YourMonadType a a
deriving stock (Functor, Show)
deriving Applicative via (WrappedMonad YourMonadType)
instance Monad YourMonadType where
return x = YourMonadType x x
YourMonadType x y >>= f = YourMonadType fx fy
where YourMonadType fx _ = f x
YourMonadType _ fy = f y
ghci> YourMonadType ( 1) (*2) <*> YourMonadType 10 20
YourMonadType 11 40