This is a general question, not tied to any one piece of code.
Say you have a type T a
that can be given an instance of Monad
. Since every monad is an Applicative
by assigning pure = return
and (<*>) = ap
, and then every applicative is a Functor
via fmap f x = pure f <*> x
, is it better to define your instance of Monad
first, and then trivially give T
instances of Applicative
and Functor
?
It feels a bit backward to me. If I were doing math instead of programming, I would think that I would first show that my object is a functor, and then continue adding restrictions until I have also shown it to be a monad. I know Haskell is merely inspired by Category Theory and obviously the techniques one would use when constructing a proof aren't the techniques one would use when writing a useful program, but I'd like to get an opinion from the Haskell community. Is it better to go from Monad
down to Functor
? or from Functor
up to Monad
?
I tend to write and see written the Functor
instance first. Doubly so because if you use the LANGUAGE DeriveFunctor
pragma then data Foo a = Foo a deriving ( Functor )
works most of the time.
The tricky bits are around agreement of instances when your Applicative
can be more general than your Monad
. For instance, here's an Err
data type
data Err e a = Err [e] | Ok a deriving ( Functor )
instance Applicative (Err e) where
pure = Ok
Err es <*> Err es' = Err (es ++ es')
Err es <*> _ = Err es
_ <*> Err es = Err es
Ok f <*> Ok x = Ok (f x)
instance Monad (Err e) where
return = pure
Err es >>= _ = Err es
Ok a >>= f = f a
Above I defined the instances in Functor
-to-Monad
order and, taken in isolation, each instance is correct. Unfortunately, the Applicative
and Monad
instances do not align: ap
and (<*>)
are observably different as are (>>)
and (*>)
.
Err "hi" <*> Err "bye" == Err "hibye"
Err "hi" `ap` Err "bye" == Err "hi"
For sensibility purposes, especially once the Applicative/Monad Proposal is in everyone's hands, these should align. If you defined instance Applicative (Err e) where { pure = return; (<*>) = ap }
then they will align.
But then, finally, you may be capable of carefully teasing apart the differences in Applicative
and Monad
so that they behave differently in benign ways---such as having a lazier or more efficient Applicative
instance. This actually occurs fairly frequently and I feel the jury is still a little bit out on what "benign" means and under what kinds of "observation" should your instances align. Perhaps some of the most gregarious use of this is in the Haxl project at Facebook where the Applicative
instance is more parallelized than the Monad
instance, and thus is far more efficient at the cost of some fairly severe "unobserved" side effects.
In any case, if they differ, document it.