Referential Transparency
The great introduction of Learn You a Haskell for Great Good!, that we read on day 1, has a paragraph that I’d like to quote here:.
So in purely functional languages, a function has no side effects. The only thing a function can do is calculate something and return it as a result. [...] if a function is called twice with the same parameters, it’s guaranteed to return the same result. That’s called referential transparency
If our way of processing and thinking is just a tiny bit similar, this would trigger you too. At first, while reading, it was no big deal. The author explained a concept and eventually gave that concept a name. However something kept bugging me at night, knowing that I just stumbled over something I've never heard before and can't just take it for granted without knowing more of that concept's background. And the concept sounds so fancy. I've done some functional programming basics in the past, why am I hearing it for the first time now?
The Big Confusion
First things first, I opened Wikipedia and the corresponding article about referential transparency. And it reads like this:
In analytic philosophy and computer science, referential transparency and referential opacity are properties of linguistic constructions, and by extension of languages. A linguistic construction is called referentially transparent when for any expression built from it, replacing a subexpression with another one that denotes the same value does not change the value of the expression.
Well, that doesn't sound so simple like our books author tried to break it down for us anymore. Things aren’t any clearer, and not a single question was answered. My biggest issue with that: I don't understand anything of it. There are a lot of technical (or linguistic) terms that I need to break down first, and there is the reference to the linguistic and philosophical construction. Maybe it's a good idea for me to understand the linguistic part first and switch back to the technical one at a later time. Interesting to know that not only mathematical but also linguistic concepts are being borrowed in functional programming.
Eventually I landed on the Wikipedia article about opaque context in linguistics. And wow, that is poorly written (little insider joke here between linguists and philosophers wink). While the general concepts become much clearer after going through that article, one main question still remains after reading the first example:
An opaque context or referentially opaque context is a linguistic context in which it is not always possible to substitute "co-referential" expressions. [...] substitution of co-referential expressions into an opaque context does not always preserve truth. For example, "Lois believes x is a hero" is an opaque context because "Lois believes Superman is a hero" is true while "Lois believes Clark Kent is a hero" is false, even though 'Superman' and 'Clark Kent' are co-referential expressions.
And this is a bad example for a reason, especially if this is the first time the reader is being confronted with the concept. Why is the first sentence, Lois believing Superman is a hero, true, but the second sentence, Lois believing Clark Kent is a hero, false? Seems to be perfectly substitutable to me at first. So it is a bad example because the author assumes that the reader has enough context to understand who Lois, Superman, and Clark Kent are.
I've read and researched a little bit more and further, went through other sources too. By far the best one was the book "The Linguistic Description of Opaque Contexts by Janet Dean Fodor (1964)". In the first paragraph of the abstract she nailed it already. She explained it in simple words. And as we all know, explaining a complicated concept with simple words is the most difficult task on this planet. Janet Dean Fodor describes it as follows.
[...] With some exceptions, opaque constructions are sentences containing sentential complements [...]
That's when I've realized that in order to understand the Wikipedia example I need to understand:
Who is Lois?
A quick research and we find almost immediately an article called "Superman & Lois". Okay great, so they seem to be related, it isn't like a random person saying those two statements. For example if I would say
Rick believes Superman is a hero
and
Rick believes Clark Kent is a hero
both sentences would always be true, as long as the constant "Rick" doesn't change. Under this condition they would be referential transparent. However, as Janet Dean Fodor said, opaque constructions contain sentential complements. In our case here the sentential complement is "believes". So by simply substituting the singular term "Rick" in this sentence with the singular term "Lois", the rest of the sentence may or may not remain true.. Because who guarantees that Lois also knows that Superman's alter ego is Clark Kent? Probably she doesn't and that's why the example was chosen for the Wikipedia article.
Change Opaque into Transparent
So, in purely linguistic terms let's try to rebuild this sentence so that it has no sentential complement and becomes referentially transparent by definition. As we have already identified the ambiguity here, let's simply remove it and try it again.
Superman is a hero
Now here we can substitute "superman" without a doubt with his co-referential expression, his alter ego: Clark Kent.
Clark Kent is a hero
By simply getting rid of the sentential part of the expression we achieved referential transparency. Great, but what does this mean in terms of computer science now? Before understanding what referential transparency is, let's focus on the opaque part first.
Referential Opacity in Computer Science
Let's take as an example a counter, every time the user clicks on a button we increase the counter value by 1. As per definition the entire implementation can't be represented without a state or a side-effect. The easiest way I can think of is: We have a state counter and every time we call the function increment we increase the value of counter by 1.
let counter = 0
const increment = () => {
return counter++
}
Let's try to describe the process of setting this counter to the value of 10:
We believe the value of counter is 10 after 10 increment iterations
Why believe? Because we are mutating external state. A variable that is outside the function definition. A sentient.
Let's try to rephrase it into a referential transparent sentence:
The value of counter is 10 when increment is called with a value of 9
Or in code
const increment = (previous: number) => {
return previous + 1
}
const counter = increment(9)
We just got rid of the assumption that calling the same function 10 times will most likely give us the result we expect. Instead our new approach here is, no matter how often I call this function with the same parameter, or co-referential expressions of that parameter, the output will always be the same.
You are right, this raises a new issue: State.
But with only one hour a day (today's time invested is already more like 3 or 4 hours), we can't tackle this today. Let's tackle it in chronological order, whenever we will need state for the first time.
Take care!
Rick.