Reputation: 4832
Indeed it does:
λ :i Applicative
class Functor f => Applicative (f :: * -> *) where
At the same time:
fmap f x = pure f <*> x
— by the laws of Applicative
we can define fmap
from pure
& <*>
.
I don't get why I should tediously define fmap
every time I want an Applicative
if, really, fmap
can be automatically set up in terms of pure
and <*>
.
I gather it would be necessary if pure
or <*>
were somehow dependent on the definition of fmap
but I fail to see why they have to.
Upvotes: 3
Views: 212
Reputation: 12133
While there are proposals to make it's easier https://ghc.haskell.org/trac/ghc/wiki/IntrinsicSuperclasses the "default instances" problem itself is very difficult.
One challenge is how to deal with common superclasses:
fmap f x = pure f <*> x -- using Applicative
fmap f x = runIdentity (traverse (Identity . f) x) -- using Traversable
fmap f x = x >>= (return . f) -- using Monad
Which one to pick?
So the best we can do now is to provide fmapDefault
(as Data.Traversable
) does; or use pure f <*> x
; or fmapRep
from Data.Functor.Rep
when applicable.
Upvotes: 2
Reputation: 92117
While fmap
can be derived from pure
and <*>
, it is generally not the most efficient approach. Compare:
fmap :: (a -> b) -> Maybe a -> Maybe b
fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)
with the work done using Applicative tools:
fmap :: (a -> b) -> Maybe a -> Maybe b
-- inlining pure and <*> in: fmap f x = pure f <*> x
fmap f x = case (Just f) of
Nothing -> Nothing
Just f' -> case x of
Nothing -> Nothing
Just x' -> Just (f' x')
Pointlessly wrapping something up in a constructor just to do a pattern-match against it.
So, clearly it is useful to be able to define fmap
independently of the Applicative functions. That could be done by making a single typeclass with all three functions, using a default implementation for fmap
that you could override. However, there are types that make good Functor instances but not good Applicative instances, so you may need to implement just one. Thus, two typeclasses.
And since there are no types with Applicative instances but without Functor instances, you should be able to treat an Applicative as though it were a Functor, if you like; hence the extension relationship between the two.
However, if you tire of implementing Functor, you can (in most cases) ask GHC to derive the only possible implementation of Functor for you, with
{-# LANGUAGE DeriveFunctor #-}
data Boring a = Boring a deriving Functor
Upvotes: 8