8

I understand different notions of functional programming by itself: side effects, immutability, pure functions, referential transparency. But I can't connect them together in my head. For example, I have the following questions:

  1. What is the relation between ref. transparency and immutability. Does one implies the other?

  2. Sometimes side effects and immutability is used interchangeably. Is it correct?

kiritsuku
  • 52,967
  • 18
  • 114
  • 136
Konstantin Milyutin
  • 11,946
  • 11
  • 59
  • 85

3 Answers3

8

This question requires some especially nit-picky answers, since it's about defining common vocabulary.

First, a function is a kind of mathematical relation between a "domain" of inputs and a "range" (or codomain) of outputs. Every input produces an unambiguous output. For example, the integer addition function + accepts input in the domain Int x Int and produces outputs in the range Int.

object Ex0 {
  def +(x: Int, y: Int): Int = x + y
}

Given any values for x and y, clearly + will always produce the same result. This is a function. If the compiler were extra clever, it could insert code to cache the results of this function for each pair of inputs, and perform a cache lookup as an optimization. That's clearly safe here.

The problem is that in software, the term "function" has been somewhat abused: although functions accept arguments and return values as declared in their signature, they can also read and write to some external context. For example:

class Ex1 {
  def +(x: Int): Int = x + Random.nextInt
}

We can't think of this as a mathematical function anymore, because for a given value of x, + can produce different results (depending on a random value, which doesn't appear anywhere in +'s signature). The result of + can not be safely cached as described above. So now we have a vocabulary problem, which we solve by saying that Ex0.+ is pure, and Ex1.+ isn't.

Okay, since we've now accepted some degree of impurity, we need to define what kind of impurity we're talking about! In this case, we've said the difference is that we can cache Ex0.+'s results associated with its inputs x and y, and that we can't cache Ex1.+'s results associated with its input x. The term we use to describe cacheability (or, more properly, substitutability of a function call with its output) is referential transparency.

All pure functions are referentially transparent, but some referentially transparent functions aren't pure. For example:

object Ex2 {
  var lastResult: Int
  def +(x: Int, y: Int): Int = {
    lastResult = x + y
    lastResult
  }
}

Here we're not reading from any external context, and the value produced by Ex2.+ for any inputs x and y will always be cacheable, as in Ex0. This is referentially transparent, but it does have a side effect, which is to store the last value computed by the function. Somebody else can come along later and grab lastResult, which will give them some sneaky insight into what's been happening with Ex2.+!

A side note: you can also argue that Ex2.+ is not referentially transparent, because although caching is safe with respect to the function's result, the side effect is silently ignored in the case of a cache "hit." In other words, introducing a cache changes the program's meaning, if the side effect is important (hence Norman Ramsey's comment)! If you prefer this definition, then a function must be pure in order to be referentially transparent.

Now, one thing to observe here is that if we call Ex2.+ twice or more in a row with the same inputs, lastResult will not change. The side effect of invoking the method n times is equivalent to the side effect of invoking the method only once, and so we say that Ex2.+ is idempotent. We could change it:

object Ex3 {
  var history: Seq[Int]
  def +(x: Int, y: Int): Int = {
    result = x + y
    history = history :+ result
    result
  }
}

Now, each time we call Ex3.+, the history changes, so the function is no longer idempotent.

Okay, a recap so far: a pure function is one that neither reads from nor writes to any external context. It is both referentially transparent and side effect free. A function that reads from some external context is no longer referentially transparent, whereas a function that writes to some external context is no longer free of side effects. And finally, a function which when called multiple times with the same input has the same side effect as calling it only once, is called idempotent. Note that a function with no side effects, such as a pure function, is also idempotent!

So how does mutability and immutability play into all this? Well, look back at Ex2 and Ex3. They introduce mutable vars. The side effects of Ex2.+ and Ex3.+ is to mutate their respective vars! So mutability and side effects go hand-in-hand; a function that operates only on immutable data must be side effect free. It might still not be pure (that is, it might not be referentially transparent), but at least it will not produce side effects.

A logical follow-up question to this might be: "what are the benefits of a pure functional style?" The answer to that question is more involved ;)

Community
  • 1
  • 1
mergeconflict
  • 8,156
  • 34
  • 63
  • Very good description of 'pure', thanks for that. I disagree with your example `Ex2` with respect to referential transparency, though: this side effect is visible to the rest of the program (as it may read `lastResult`), which means for example applying memoization to this method will change the outcome/observable behaviour of the program. That makes this method not referentially transparent - I don't know of any definitions that disagree. – Arnout Engelen Aug 25 '12 at 08:35
  • The grey area you might be thinking of is a private cache, not accessible to the rest of the program: a method that stores intermediate results in such a cache does have a side effect (filling the cache), but applying e.g. memoization does not change the outcome of the program. Whether such a method is considered 'referentially transparent' is indeed debatable. – Arnout Engelen Aug 25 '12 at 08:36
  • As @ArnoutEngelen already said, it is a bit confusing what you mean with referentially transparent <-> side effects. Can you provide an example which is referentially transparent but has no side effects and is not pure? – kiritsuku Aug 25 '12 at 08:50
  • You are mistaken about the meaning of "idempotent". It means that f(f(x)) = f(x), which has nothing to do with purity. Also, reading from a context does not affect purity as long as the part you read is itself immutable. – Andreas Rossberg Aug 25 '12 at 09:25
  • @AndreasRossberg The `f(f(x)) = f(x)` definition of idempotence can be rephrased as `f . f = f`, an instance of the more general idempotence of a binary operator (in this case function composition). If you take that generalization a step further to Kleisli composition, you pretty much get the common computer science definition of idempotence. – Mysterious Dan Jun 23 '13 at 04:58
  • @MyseriousDan, my comment was directed at the sentence _"Note that a function with no side effects, such as a pure function, is also idempotent!"_ in the answer (and the one before), which seems to say that every pure function is idempotent (and implies that idempotence is a matter of equivalent effects). – Andreas Rossberg Jun 23 '13 at 07:46
  • @AndreasRossberg Oh, fair enough. I guess there are a couple of concepts here then: `f . f = f` is the standard definition, and if you let the composition be in a category like Kleisli then you can talk about effects too. Then, for lack of a better notation than Haskell's, we have `f >> f = f` (where `f :: m a` for some `Monad m`) which is I think what that sentence was getting at (assuming you take two pure functions, apply them and throw them into your monad with `pure` or similar). All related, but the obvious reading definitely fails there, you're right. – Mysterious Dan Jun 23 '13 at 15:59
  • I should point out that I've glossed over some rather important details. Usually, we talk about idempotent operators, and my usage above is talking about elements that are idempotent under some operator. So set union might be an idempotent operator. The identity function is idempotent under function composition. There's probably a more precise term for this distinction floating around somewhere. – Mysterious Dan Jun 23 '13 at 16:09
2

"No" to the first - one implies the other, but not the converse, and a qualified "Yes" to the second.

  1. "An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program". Immutable input suggests that an expression (function) will always evaluate to the same value, and therefore be referentially transparent.

    However, (mergeconflict has kindly correct me on this point) being referentially transparent does not necessarily require immutability.

  2. By definition, a side-effect is an aspect of a function; meaning that when you call a function, it changes something. Immutability is an aspect of data; it cannot be changed. Calling a function on such does imply that there can be no side-effects. (in Scala, that's limited to "no changes to the immutable object(s)" - the developer has responsibilities and decisions).

    While side-effect and immutability don't mean the same thing, they are closely related aspects of a function and the data the function is applied to.

Since Scala is not a pure functional programming language, one must be careful when considering the meaning of such statements as "immutable input" - the scope of the input to a function may include elements other than those passed as parameters. Similarly for considering side-effects.

Richard Sitze
  • 8,262
  • 3
  • 36
  • 48
  • I like your clarification in (2) that side effects are associated with functions, whereas immutability is associated with data, but I think your explanation in (1) is wrong. Referential transparency doesn't directly relate to mutability, rather it relates to environment, as indicated by the quotation you included. – mergeconflict Aug 24 '12 at 23:13
  • @mergeconflict thank you for the correction, and the details in your response! Answer adjusted accordingly. – Richard Sitze Aug 25 '12 at 00:00
1

It rather depends on the specific definitions you use (there can be disagreement, see for example Purity vs Referential transparency), but I think this is a reasonable interpretation:

Referential transparency and 'purity' are properties of functions/expressions. A function/expression may or may not have side-effects. Immutability, on the other hand, is a property of objects, not functions/expressions.

Referential transparency, side-effects and purity are closely related: 'pure' and 'referentially transparent' are equivalent, and those notions are equivalent to the absence of side-effects.

An immutable object may have methods which are not referentially transparent: those methods will not change the object itself (as that would make the object mutable), but may have other side-effects such as performing I/O or manipulating their (mutable) parameters.

Community
  • 1
  • 1
Arnout Engelen
  • 6,709
  • 1
  • 25
  • 36