Referential Transparency

Aug 12 2016

Referential transparency is a property of an expression, where an expression can be replaced with the value it evaluates to. (Related: Pure functions)

It can be expressed with the following equation.

f(x) = f(x)

Thus, if we have the expression (* 10 10), it can be replaced with the value it evaluates to without changing any behaviour of the system.

If we put this expression into the equation, we see that the equation is satisfied.

(= (* 10 10) (* 10 10))
;=> (= 100 100)
;=> true

Looking at another example, this implementation of fib is referentially transparent.

(defn fib [n]
  (if (< n 2)
    n
    (+ (fib (- n 1)) (fib (- n 2)))))

Regardless of how many times we call (fib 8), we always get 21.

(= (fib 8) (fib 8))
; => (= 21 21)
; => true

The implication here, however, is that a for an expression to be referentially transparent, it must be void of side effects or temporal coupling.

Referential Opacity1

Let’s look at some examples of functions that are not referentially transparent.

Side effects

First up, count-up.

(def counter (atom 0))

(defn count-up []
   (swap! counter inc))

count-up is not referentially transparent because the value it evaluates to changes every time the function is called. The reason that it does not always evaluate to the same value is because it is affecting counter and returning counter’s current state.

(= (count-up) (count-up))
; => (= 1 2)
; => false

Temporal Coupling

This next example demonstrates how temporal coupling violates referential transparency.

(defn true-later []
  (if (< (date 2086 8 11) (now))
    true
    false))

If we evaluated this function now, and then waited 70 years to evaluate it again, would we get the same result? No, we would not.

(= (evaluated-now true-later) (evaluated-in-70-years true-later))
;=> (= true false)
;=> false

Hidden side effects

Finally, we have an example that is a bit more opaque.

(defn add [a b]
  (println "Adding " a " and " b)
  (+ a b))

It is true that the value that this function will always evaluate to the same value given the same set of inputs.

(= (add 1 1) (add 1 1))
"Adding 1 and 1"
; => (= 2 2)
; => true

Oh so this must be referentially transparent! Not quite.

If we replaced this function with the value it evaluates to, we would no longer be printing the message. Thus, we would be missing behaviour from the original implementation.

Wrap up

This is just an explanation of what referential transparency is, but not how it can be useful. It is often claimed that referentially transparent programs are easier to reason about[HW], but I am not qualified to talk about that yet.

Referential transparency is enforced by some functional programming languages[C2]. However, you don’t need a language that enforces referential transparency in order to write referentially transparent code.


Additional Reading


Sources

HW Referential transparency. (2009, March 29). In HaskelWiki. Retrieved August 12, 2016, from https://wiki.haskell.org/Referential_transparency

C2. Referential Transparency. (2010, July 10). In WikiWikiWeb. Retrieved August 12, 2016, from http://c2.com/cgi/wiki?ReferentialTransparency


Footnotes

  1. No one seems to use this term to refer to expression that are not referential transparent. A quick Google search only yielded results relating to social sciences.
tags:referential transparency

Recent Articles