Question

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...?

Was it helpful?

Solution 2

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.

OTHER TIPS

It is not true that Functors 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.

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top