What are applicative effects?
In the FP world, an effect is any type constructor such as Maybe
, []
, IO
, etc. Effects are not to be confused with side-effects. Intuitively, an effect is an additional property of the value you're computing. Maybe Int
means that your program calculates an Int
with a failure effect, or [Int]
means that your program calculates an Int
but with a non-deterministic effect (a non-deterministic result is here modeled as a list of possible results).
Going from here, we have the terms applicative effects and monadic effects, which mean that the said effects have Applicative
and Monad
instances.
I can't find any authoritative information for this, it's just what I have gleaned in my experience.
Much confusion was caused by the unfortunate choice of names, as is quite common in Haskell (think "return
", much better named "emit
").
pure x
is not pure, it is x
that is pure, pure
is just inject. It was envisioned to be used in pure f <*> a <*> b <*> ...
pattern, letting us effectfully apply a pure function f
.(1)
[]
applicative(2) lets us "non-deterministically" apply (<*>
, not $
) a non-deterministic value (not two values, in your example) to a non-deterministic function; the non-determinism is the effect.(3)
In list applicative, [(+1), (+2)]
is a non-deterministic function that might increment a value by 1, and also might increment it by 2. [3,4,5]
is a non-deterministic value whose possible values are listed. Just as we apply normal entities (+1)
and 3
normally, as (+1) $ 3
, so can we apply non-deterministic values non-deterministically, as [(+1)] <*> [3]
or [(+1),(+2)] <*> [3,4,5]
.
And with Maybe
the possibility of failure is the effect.
(1) as the paper says, in Introduction: "we collect the values of some effectful computations, which we then use as the arguments to a pure function (:)
"
(2) []
by itself is not an applicative, ([], pure :: a -> [a], (<*>) :: [a -> b] -> [a] -> [b])
is an applicative, given some (lawful) implementations of pure
and (<*>)
.
(3) x
is pure (as in, "Haskell is pure"); pure x
stands for an effectful computation producing x
without actually having any additional effect. "without an effect" refers to the law of pure x *> u == u
i.e. pure x
doesn't add any effect into the combined computation on top of u
's contribution. But the possibility of effect is there.
pure 7 :: IO Int
is certainly not the pure (as in, "Haskell is pure") value 7
, it is the pure value 7
in the effectful context (IO
). Even if it does no effectful action in that context, it's still in that context (IO
).
on the other hand, and unrelated to the purpose of pure
, of course any Haskell value is pure and referentially transparent. getLine
is a pure, referentially transparent Haskell value. It stands for an effectful I/O computation, getting an input line from a user and producing it as the result to be used by the next I/O computation.
print 7
is a pure referentially transparent Haskell value. that's not a kind of "pure" that is meant here. [1,2]
is a pure value, but seen from another angle it's a nondeterministic value with two possible pure values 1
and 2
. Same for [1]
. It can still be interpreted as a nondeterministic value with one possible pure value, 1
.
[1,2] *> [10,20]
= [10,20,10,20]
; [1] *> [10,20]
= [10,20]
. So unlike [1,2]
, [1]
doesn't add any nondeterminism into the nondeterministic computation described by [10,20]
. But it's still a nondeterministic value, it can participate in *>
. 1
can't. ([1]
is of course the same as pure 1
).
We know a type by what kind of interactions it can participate in.
Or, as the user @bob puts it (in the comments), "pure x
puts a pure x
into an effectful context without actually performing [any] effect".
See also:
- values and computations
We could say that an effect of type f a
is anything that can't be written as pure x
where x :: a
.
In the []
applicative, pure x = [x]
, so [(+1)] = pure (+1)
probably shouldn't be considered an effect. Similarly in the Maybe
applicative, pure = Just
, so Just (+1)
is not an effect.
That leaves [2,3]
and Nothing
as the effects in your respective examples. This makes intuitive sense from the perspective that []
denotes nondeterministic computations: [2,3]
nondeterministically chooses between 2 and 3; and the perspective that Maybe
denotes failing computations: Nothing
fails the computation.
The definition I used that an effect (perhaps "side-effect" would be a better word) is something that can't be written as pure x
is just a swing at making your question precise, and does not represent any sort of consensus or standard definition. Will Ness's answer gives a different perspective, that pure
generates an effectful computation from a pure value, which has a nice mathematical ring to it -- i.e. this definition would probably be easier to use in precise settings.