Is it safe to say that the following dichotomy holds:
Each given function is
- either pure
- or has side effects
If so, side effects (of a function) are anything that can't be found in a pure function.
This very much depends on the definitions that you choose. It is definitely fair to say that a function is pure or impure. A pure function always returns the same result and does not modify the environment. An impure function can return different results when it is executed repeatedly (which can be caused by doing something to the environment).
Are all impurities side-effects? I would not say so - a function can depend on something in the environment in which it executes. This could be reading some configuration, GPS location or reading data from the internet. These are not really "side-effects" because it does not do anything to the world.
I think there are two different kinds of impurities:
Output impurity is when a function does something to the world. In Haskell, this is modelled using monads - an impure function
a -> b
is actually a functiona -> M b
whereM
captures the other things that it does to the world.Input impurity is when a function requires something from the environment. An impure function
a -> b
can be modelled as a functionC a -> b
where the typeC
captures other things from the environment that the function may access.
Monads and output impurities are certainly better known, but I think input impurities are equally important. I wrote my PhD thesis about input impurities which I call coeffects, so I this might be a biased answer though.
For a function to be pure it must:
- not be affected by side-effects (i.e. always return same value for same arguments)
- not cause side-effects
But, you see, this defines functional purity with the property or absence of side-effects. You are trying to apply backwards logic to deduct the definition of side-effects using pure functions, which logically should work, but practically the definition of a side-effect has nothing to do with functional purity.
I don't see problems with the definition of a pure function: a pure function is a function. I.e. it has a domain, a codomain and maps the elements of the former to the elements of the latter. It's defined on all inputs. It doesn't do anything to the environment, because "the environment" at this point doesn't exist: there are no machines that can execute (for some definition of "execute") a given function. There is just a total mapping from something to something.
Then some capitalist decides to invade the world of well-defined functions and enslave such pure creatures, but their fair essence can't survive in our cruel reality, functions become dirty and start to make the CPU warmer.
So it's the environment is responsible for making the CPU warmer and it makes perfect sense to talk about purity before its owner was abused and executed. In the same way referential transparency is a property of a language — it doesn't hold in the environment in general, because there can be a bug in the compiler or a meteorite can fall upon your head and your program will stop producing the same result.
But there are other creatures: the dark inhabitants of the underworld. They look like functions, but they are aware of the environment and can interact with it: read variables, send messages and launch missiles. We call these fallen relatives of functions "impure" or "effectful" and avoid as much as possible, because their nature is so dark that it's impossible to reason about them.
So there is clearly a big difference between those functions which can interact with the outside and those which doesn't. However the definition of "outside" can vary too. The State
monad is modeled using only pure tools, but we think about f : Int -> State Int Int
as about effectful computation. Moreover, non-termination and exceptions (error "..."
) are effects, but haskellers usually don't consider them so.
Summarizing, a pure function is a well-defined mathematical concept, but we usually consider functions in programming languages and what is pure there depends on your point of view, so it doesn't make much sense to talk about dichotomies when involved concepts are not well-defined.
A way to define purity of a function f
is ∀x∀y x = y ⇒ f x = f y
, i.e. given the same argument the function returns the same result, or it preserves equality.
This isn't what people usually mean when they talk about "pure functions"; they usually mean "pure" as "does not have side effects". I haven't figured out how to qualify a "side effect" (comments welcome!) so I don't have anything to say about it.
Nonetheless, I'll explore this concept of purity because it might offer some related insight. I'm no expert here; this is mostly me just rambling. I do however hope it sparks some insightful (and corrective!) comments.
To understand purity we have to know what equality we are talking about. What does x = y
mean, and what does f x = f y
mean?
One choice is the Haskell semantic equality. That is, equality of the semantics Haskell assigns to its terms. As far as I know there are no official denotational semantics for Haskell, but Wikibooks Haskell Denotational Semantics offers a reasonable standard that I think the community more or less agrees to ad-hoc. When Haskell says its functions are pure this is the equality it refers to.
Another choice is a user-defined equality (i.e. (==)
) by deriving the Eq
class. This is relevant when using denotational design — that is, we are assigning our own semantics to terms. With this choice we can accidentally write functions which are impure; Haskell is not concerned with our semantics.
I will refer to the Haskell semantic equality as =
and the user-defined equality as ==
. Also I assume that ==
is an equality relation — this does not hold for some instances of ==
such as for Float
.
When I use x == y
as a proposition what I really mean is x == y = True ∨ x == y = ⊥
, because x == y :: Bool
and ⊥ :: Bool
. In other words, when I say x == y
is true, I mean that if it computes to something other than ⊥ then it computes to True.
If x
and y
are equal according to Haskell's semantics then they are equal according to any other semantic we may choose.
Proof: if x = y
then x == y ≡ x == x
and x == x
is true because ==
is pure (according to =
) and reflexive.
Similarly we can prove ∀f∀x∀y x = y ⇒ f x == f y
. If x = y
then f x = f y
(because f
is pure), therefore f x == f y ≡ f x == f x
and f x == f x
is true because ==
is pure and reflexive.
Here is a silly example of how we can break purity for a user-defined equality.
data Pair a = Pair a a
instance (Eq a) => Eq (Pair a) where
Pair x _ == Pair y _ = x == y
swap :: Pair a -> Pair a
swap (Pair x y) = Pair y x
Now we have:
Pair 0 1 == Pair 0 2
But:
swap (Pair 0 1) /= swap (Pair 0 2)
Note:
¬(Pair 0 1 = Pair 0 2)
so we were not guaranteed that our definition of(==)
would be okay.
A more compelling example is to consider Data.Set
. If x, y, z :: Set A
then you would hope this holds, for example:
x == y ⇒ (Set.union z) x == (Set.union z) y
Especially when Set.fromList [1,2,3]
and Set.fromList [3,2,1]
denote the same set but probably have different (hidden) representations (not equivalent by Haskell's semantics). That is to say we want to be sure that ∀z Set.union z
is pure according to (==)
for Set
.
Here is a type I have played with:
newtype Spry a = Spry [a]
instance (Eq a) => Eq (Spry a) where
Spry xs == Spry ys = fmap head (group xs) == fmap head (group ys)
A Spry
is a list which has non-equal adjacent elements. Examples:
Spry [] == Spry []
Spry [1,1] == Spry [1]
Spry [1,2,2,2,1,1,2] == Spry [1,2,1,2]
Given this, what is a pure implementation (according to ==
for Spry) for flatten :: Spry (Spry a) -> Spry a
such that if x
is an element of a sub-spry it is also an element of the flattened spry (i.e. something like ∀x∀xs∀i x ∈ xs[i] ⇒ x ∈ flatten xs
)? Exercise for the reader.
It is also worth noting that the functions we've been talking about are across the same domain, so they have type A → A
. That is except for when we proved ∀f∀x∀y x = y ⇒ f x == f y
which crosses from Haskell's semantic domain to our own. This might be a homomorphism of some sorts… maybe a category theorist could weigh in here (and please do!).
Side effects are part of the definition of the language. In the expression
f e
the side effects of e
are all the parts of e
's behavior that are 'moved out' and become part of the behavior of the application expression, rather than being passed into f
as part of the value of e
.
For a concrete example, consider this program:
f x = x; x
f (print 3)
where conceptually the syntax x; x
means 'run x, then run it again and return the result'.
In a language where print
writes to stdout as a side effect, this writes
3
because the output is part of the semantics of the application expression.
In a language where the output of print
is not a side effect, this writes
3
3
because the output is part of the semantics of the x
variable inside the definition of f
.
来源:https://stackoverflow.com/questions/36969344/are-side-effects-everything-that-cannot-be-found-in-a-pure-function