All you need to know about Referential Transparency

Reading Time: 2 minutes

In this blog, we’ll talk about referential transparency and why it’s useful for us as programmers. The short version is this: referential transparency is a term for “replaceable code”. In this blog we’ll understand what that means, So lets begin with introduction of referential transparency.

What is Referential Transparency?

In functional programming, referential transparency is generally defined as the fact that an expression, in a program, may be replaced by its value without changing the result of the program. This implies that methods should always return the same value for a given argument, without having any other effect. This functional programming concept also applies to imperative programming, though, and can help you make your code clearer.

Referential transparency is a concept which works close with pure functions and immutability
since your program has fewer assignment statements, and often when you have it, you tend to
never change that value. This is great because you eliminate side effects with this technique.
During program execution, any variable can be replaced since there are no side effects, and
the program becomes referentially transparent. Scala language makes this concept very clear
the moment you declare a variable.

def add(a: Int, b: Int) = a + b

This function is referentially transparent. Because we can replace all occurrences of this function with the expression it evaluates to, and then with the value it computes, at any point in our program.

val equal = add(2,3)

An expression is referentially transparent if we can do this back-and-forth replacement anytime anywhere, without changing the meaning and output of our program.

What is NOT Referential Transparent?

On other hand, we will see an example which is not referential transparent.

for{
  _ <- Future(println("Hello"))
  _ <- Future(println("Hello"))
} yield()
//Hello
//Hello

You will observe that the above computation prints “Hello” on the console TWICE.

Now, let’s assign it in the val:

val sayHello = Future(println("Hello")
for{
  _ <- sayHello
  _ <- sayHello
} yield ()
//Hello

You will observe that the above computation prints “Hello” on the console ONCE.

This clearly violates the rule of referential transparency. We got different behavior from the same function when we replace the expression by its value.

Conclusion

Referential transparency is the ability to freely replace an expression with its value and not change the behaviour of the program. Its main benefit is the compiler being able to do something with an expression. If an expression’s output is unpredictable, a compiler can’t optimize the code at all, and the program must sadly wait to see what the expression evaluates to at runtime.