Why can applicative functors have side effects, but functors can't?
Asked Answered
D

4

18

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

Dickson answered 29/1, 2013 at 5:56 Comment(4)
What do you mean by “have side effects”?Zoroaster
Every monad is also a functor, so you'll have to be more specific...Crony
The original paper, "Applicative Programming with effects", is highly readable and explains the extra power applicative functors provide, with several examples. strictlypositive.org/Idiom.pdfPearcy
They can and you've just never noticed! I like to use Functors with IO to apply a pure function like this: fmap (unlines.reverse.lines) $ readFile "chronological_log.txt". I tend to use the infix operator for fmap, <$> imported from Data.Functor (or Control.Applicative!) so I'd normally write unlines.reverse.lines <$> readFile "chronological_log.txt".Fritzie
A
25

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.

Albuminuria answered 29/1, 2013 at 10:15 Comment(3)
Thanks shang, I had a suspicion it had to do with chaining effects, but I wasn't sure. I appreciate you writing that out!Dickson
If you "define side effects as computations being affected by previous computations" then you could argue that Applicative doesn't have side effects either - you'd need a full monad for that. Applicative chains them together, but Monad allows the output from the first to affect the action the second performs. I can't think at the moment of a way to rephrase that to say what I think you mean. (I think you mean stuff like flip const <$> writeFile "temp.txt" "Hello" <*> readFile "temp.txt" where the action of the first affects the result of the second.)Fritzie
Yes, it would be more accurate to say that for applicatives the end result of computations can be affected by preceding computations. As opposed to monads where the end result of a previous computation can be used to select the next computation.Albuminuria
B
31

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.

Brough answered 29/1, 2013 at 10:57 Comment(2)
Excellent, to-the-point answer, with a clear summary of the Functor, Applicative and Monad classes. I'd upvote more than once if I could.Fritzie
Thanks Petr! This is a great explanation. I accepted the other answer as it had more votes, but I think this is awesome. I appreciate the example.Dickson
A
25

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.

Albuminuria answered 29/1, 2013 at 10:15 Comment(3)
Thanks shang, I had a suspicion it had to do with chaining effects, but I wasn't sure. I appreciate you writing that out!Dickson
If you "define side effects as computations being affected by previous computations" then you could argue that Applicative doesn't have side effects either - you'd need a full monad for that. Applicative chains them together, but Monad allows the output from the first to affect the action the second performs. I can't think at the moment of a way to rephrase that to say what I think you mean. (I think you mean stuff like flip const <$> writeFile "temp.txt" "Hello" <*> readFile "temp.txt" where the action of the first affects the result of the second.)Fritzie
Yes, it would be more accurate to say that for applicatives the end result of computations can be affected by preceding computations. As opposed to monads where the end result of a previous computation can be used to select the next computation.Albuminuria
G
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.

Giltedged answered 30/1, 2013 at 1:43 Comment(0)
K
2

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.

Kenyatta answered 29/1, 2013 at 7:40 Comment(2)
That's not a valid Functor instance. (f x) isn't the correct type in the first 2 cases. The only thing you can do with fmap is return None. You can similarly write pure = const None to make an Applicative instance.Pearcy
@John: You are right. This isn't even a valid functor, so the choice of the implementation of pure is moot. You can't satisfy the fmap id = id law. Also your choice of pure does not satisfy the pure id <*> x = x law.Kenyatta

© 2022 - 2024 — McMap. All rights reserved.