Functional Programming – How Side Effects Break Referential Transparency

functional programmingside-effect

Functional Programming in Scala explains a side effect’s impact on breaking referential transparency:

side effect, which implies some violation of referential transparency.

I’ve read part of SICP, which discusses using the “substitution model” to evaluate a program.

As I roughly understand the substitution model with referential transparency (RT), you can de-compose a function into its simplest parts. If the expression is RT, then you can de-compose the expression and always get the same result.

However, as the above quote states, using side effects can/will break the substitution model.

Example:

val x = foo(50) + bar(10)

If foo and bar do not have side effects, then executing either function will always return the same result to x. But, if they do have side effects, they will alter a variable that disrupts/throws a wrench into the substitution model.

I feel comfortable with this explanation, but I don’t fully grok it.

Please correct me and fill in any holes with respect to side effects breaking RT, discussing the effects on the substitution model as well.

Best Answer

Let's begin with a definition for referential transparency:

An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program (in other words, yielding a program that has the same effects and output on the same input).

What that means is that (for example) you can replace 2 + 5 with 7 in any part of the program, and the program should still work. This process is called substitution. Substitution is valid if, and only if, 2 + 5 can be replaced with 7 without affecting any other part of the program.

Let's say that I have a class called Baz, with the functions Foo and Bar in it. For simplicity, we'll just say that Foo and Bar both return the value that is passed in. So Foo(2) + Bar(5) == 7, as you would expect. Referential Transparency guarantees that you can replace the expression Foo(2) + Bar(5) with the expression 7 anywhere in your program, and the program will still function identically.

But what if Foo returned the value passed in, but Bar returned the value passed in, plus the last value provided to Foo? That's easy enough to do if you store the value of Foo in a local variable within the Baz class. Well, if the initial value of that local variable is 0, the expression Foo(2) + Bar(5) will return the expected value of 7 the first time you invoke it, but it will return 9 the second time you invoke it.

This violates referential transparency two ways. First, Bar can not be counted on to return the same expression each time it is called. Second, a side-effect has occurred, namely that calling Foo influences the return value of Bar. Since you can no longer guarantee that Foo(2) + Bar(5) will equal 7, you can no longer substitute.

This is what Referential Transparency means in practice; a referentially transparent function accepts some value, and returns some corresponding value, without affecting other code elsewhere in the program, and always returns the same output given the same input.