简体   繁体   中英

Why should Applicative inherit from Functor?

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM