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...?
Functors apply a function to a wrapped value: Applicatives apply a wrapped function to a wrapped value: Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.
As I understand, every monad is a functor but not every functor is a monad. A functor takes a pure function (and a functorial value) whereas a monad takes a Kleisli arrow, i.e. a function that returns a monad (and a monadic value).
Applicative functors are the programming equivalent of lax monoidal functors with tensorial strength in category theory. Applicative functors were introduced in 2008 by Conor McBride and Ross Paterson in their paper Applicative programming with effects.
Functors are also important because they are a building block for applicatives and monads, which are coming in future posts.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With