可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm feeling rather silly asking this question, but it's been on my mind for a while and I can't find any answers.
So the question is: why can applicative functors have side effects, but functors can't?
Maybe they can and I've just never noticed...?
回答1:
This answer is a bit of an over-simplification, but if we define side effects as computations being affected by previous computations, it's easy to see that the Functor
typeclass is insufficient for side effects simply because there is no way to chain multiple computations.
class Functor f where
fmap :: (a -> b) -> f a -> f b
The only thing a functor can do is to alter the end result of a computation via some pure function a -> b
.
However, an applicative functor adds two new functions, pure
and <*>
.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
The <*>
is the crucial difference here, since it allows us to chain two computations:
f (a -> b)
(a computation which produces a function) and f a
a computation that
provides the parameter to which the function is applied. Using pure
and <*>
it's
possible to define e.g.
(*>) :: f a -> f b -> f b
Which simply chains two computations, discarding the end result from the first one
(but possibly applying "side effects").
So in short, it's the ability to chain computations which is the minimum requirement for effects such as mutable state in computations.
回答2:
It is not true that Functor
s don't have effects. Every Applicative
(and every Monad
through WrappedMonad
) is a Functor
. The main difference is that Applicative
and Monad
give you tools how to work with those effects, how to combine them. Roughly
Applicative
allows you to sequence effects and combine values inside.
Monad
in addition allows you to determine a next effect according to the result of a previous one.
However Functor
only allows you to modify the value inside, it doesn't give tools to do anything with the effect. So if something is just Functor
and not Applicative
, it doesn't mean it doesn't have effects. It just doesn't have a mechanism how to combine them in this way.
Update: As an example, consider
import Control.Applicative
newtype MyF r a = MyF (IO (r, a))
instance Functor (MyF r) where
fmap f (MyF x) = MyF $ fmap (fmap f) x
This is clearly a Functor
instance that carries effects. It's just that we don't have a way how to define operations with these effects that would comply to Applicative
. Unless we impose some additional constraints on r
, there is no way how to define an Applicative
instance.
回答3:
Other answers here have rightfully indicated that functors don't allow for side effects because they cannot be combined or sequenced, which is quite true at large, but there is one way to sequence functors: by going inward.
Let's write a limited Writer functor.
data Color = R | G | B
data ColorW a = Re a | Gr a | Bl a deriving (Functor)
and then apply the Free monad type to it
data Free f a = Pure a | Free (f (Free f a))
liftF :: Functor f => f a -> Free f a
liftF = Free . fmap Pure
type ColorWriter = Free ColorW
red, blue, green :: a -> ColorWriter a
red = liftF . Re
green = liftF . Gr
blue = liftF . Bl
Of course, by the free property, this forms a monad, but the effects are really coming from the "layers" of the functor.
interpretColors :: ColorWriter a -> ([Color], a)
interpretColors (Pure a) = ([], a)
interpretColors (Free (Re next)) = let (colors, a) = interpretColors next
in (R : colors, a)
...
So, this is sort of a trick. Really the "computation" is being introduced by the free monad but the material of the computation, the hidden context, is introduced by just a functor. It turns out you can do this with any data type, it need not even be a Functor, but Functor provides a clear way to build it.
回答4:
Let's first rename side effects to effects. All kinds of values can have effects. A functor is a type that allows you to map a function over whatever is produced by that effect.
When a functor is not applicative it doesn't allow you to use a certain composition style for the effects. Let's pick a (contrived) example:
data Contrived :: * -> * where
AnInt :: Int -> Contrived Int
ABool :: Bool -> Contrived Bool
None :: Contrived a
This is easily a functor:
instance Functor Contrived where
fmap f (AnInt x) = AnInt (f x)
fmap f (ABool x) = ABool (f x)
fmap _ None = None
However, there is no sensible implementation for pure
, so this type is not an applicative functor. It is similar to Maybe
in that it has the effect that there may not be a result value. But you can't compose it using applicative combinators.