DateTime.Now or Date.now is referential transparent?
This is one of the controversial topic in a functional programming article in Qiita.
First of all, we m
Okay, I'm going to take a stab at this. I'm not an expert on this stuff, but I've spent some time thinking about @UdayReddy's answers to this question that you linked to, and I think I've got my head wrapped around it.
I think you have to start where Mr. Reddy did in his answer to the other question. Mr. Reddy wrote:
The term "referent" is used in analytical philosophy to talk about the thing that an expression refers to. It is roughly the same as what we mean by "meaning" or "denotation" in programming language semantics.
Note the use of the word "denotation". Programming languages have a syntax, or grammar, but they also have a semantics, or meaning. Denotational semantics is the practice of translating a language's syntax to its mathematical meaning.
Denotational semantics, as far as I can tell, is not widely understood even though it is one of the most powerful tools around for understanding, designing, and reasoning about computer programs. I gotta spend a little time on it to lay the foundation for the answer to your question.
The idea behind denotational semantics is that every syntactical element in a computer language has a corresponding mathematical meaning, or semantics. Denotational semantics is the explicit mapping between syntax and semantics. Take the syntactic numeral 1
. You can map it to its mathematical meaning, which is just the mathematical number 1
. The semantic function might look like this:
syntax
↓
⟦1⟧ ∷ One
↑
semantics
Sometimes the double-square brackets are used to stand for "meaning", and in this case the number 1
on the semantic side is spelled out as One
. Those are just tools for indicating when we are talking about semantics and when we are talking about syntax. You can read that function to mean, "The meaning of the syntactic symbol 1
is the number One
."
The example that I used above looks trivial. Of course 1
means One
. What else would it mean? It doesn't have to, however. You could do this:
⟦1⟧ ∷ Four
That would be dumb, and no-one would use such a dumb language, but it would be a valid language all the same. But the point is that denotational semantics allows us to be explicit about the mathematical meaning of the programs that we write. Here is a denotation for a function that squares the integer x
using lambda notation:
⟦square x⟧ ∷ λx → x²
Now we can move on and talk about referential transparency.
Allow me to piggyback on Mr. Uday's answer again. He writes:
A context in a sentence is "referentially transparent" if replacing a term in that context by another term that refers to the same entity doesn't alter the meaning.
Compare that to the answer you get when you ask the average programmer what referential transparency means. They usually say something like the answer you quoted above:
Referential transparency, a term commonly used in functional programming, means that given a function and an input value, you will always receive the same output. That is to say there is no external state used in the function.
That answer defines referential transparency in terms of values and side effects, but it totally ignores meaning.
Here is a function that under the second definition is not referentially transparent:
var x = 0
func changeX() -> Int {
x += 1
return x
}
It reads some external state, mutates it, and then returns the value. It takes no input, returns a different value every time you call it, and it relies on external state. Meh. Big deal.
Given a correct denotational semantics, it is still referentially transparent.
Why? Because you could replace it with another expression with the same semantic meaning.
Now, the semantics of that function is much more confusing. I don't know how to define it. It has something to do with state transformations, given a state s
and a function that produces a new state s'
, the denotation might look something like this, though I have no idea if this is mathematically correct:
⟦changeX⟧ ∷ λs → (s → s')
Is that right? I have don't have a clue. Strachey figured out the denotational semantics for imperative languages, but it is complicated and I don't understand it yet. By establishing the denotative semantics, however, he established that imperative languages are every bit as referentially transparent as functional languages. Why? Because the mathematical meaning can be precisely described. And once you know the precise mathematical meaning of something, you can replace it with any other term that has the same meaning. So even though I don't know what the true semantics of the changeX
function is, I know that if I had another term with the same semantic meaning, I could swap one out for the other.
Date.now
?I don't know anything about that function. I'm not even sure what language it is from, though I suspect it may be Javascript. But who cares. What is its denotational semantics? What does it mean? What could you insert in its place without changing the meaning of your program?
The ugly truth is, most of us don't have a clue! Denotational semantics isn't that widely used to begin with, and the denotational semantics of imperative programming languages is really complicated (at least for me - if you find it easy, I'd love to have you explain it to me). Take any imperative program consisting of more than about 20 lines of non-trivial code and tell me what its mathematical meaning is. I challenge you.
By contrast the denotational semantics of Haskell is pretty straightforward. I have very little knowledge of Haskell. I've never done any coding in it beyond messing around in the ghci, but what makes it so powerful is that the syntax tracks the semantics more closely than any other language that I know of. Being a pure, strict functional language, the semantics are right there on the surface of the syntax. The syntax is defined by the mathematical concepts that define the meaning.
In fact, the syntax and semantics are so closely related that functional programmers have begun to conflate the two. (I humbly submit this opinion and await the backlash.) That is why you get definitions of referential transparency from FPers that talk about values instead of meaning. In a language like Haskell, the two are almost indistinguishable. Since there is no mutable state and every function is a pure function, all you have to do is look at the value that is produced when the function is evaluated and you've basically determined its meaning.
It may also be that the new-age FPer's explanation of referential transparency is, in a way, more useful than the one that I summarized above. And that cannot be ignored. After all, if what I wrote above is correct then everything that has a denotational semantics is referentially transparent. There is no such thing as a non-referentially transparent function, because every function has a mathematical meaning (though it may be obscure and hard to define) and you could always replace it with another term with the same meaning. What good is that?
Well, it's good for one reason. It let's us know that we don't know jack about the mathematics behind what we do. Like I said above, I haven't a clue what the denotational semantics of Date.now
is or what it means in a mathematical sense. Is it referentially transparent? Yeah, I'm sure that it is, since it could be replaced by another function with the same semantics. But I have no idea how to evaluate the semantics for that function, and therefore its referential transparency is of no use to me as a programmer.
So if there's one thing I've learned out of all of this, it is to focus a lot less on whether or not something meets some definition of "referential transparency" and a lot more on trying to make programs out of small, mathematically composable parts that have precise semantic meanings that even I can understand.