In most programming languages the reference or meaning of a term is not a value; in pure functional languages the meaning is a value and that's what makes them special, not their referential transparency which they share with imperative languages.
Here's an example from C:
int global_x = 0;
void f() { x++; }
void g() { x++; }
f and g have the same meaning in C (but the function `void h() { x += 2; }` does not) yet `m(f)` and `m(g)` will not have the same meaning if M is defined as: #define m(x) #x
However, f and g are interchangeable anywhere else (this is not actually true because their addresses can be obtained and compared; showing that a C-like language retains its referential transparency despite the existence of so-called l-values was the point of what I think is the first paper to introduce the notion referential transparency to the study of programming languages: https://github.com/papers-we-love/papers-we-love/blob/main/l... You may be surprised to see that Strachey also uses the word "value" but his point later is that value is not what you think it is)"Any discussion on the foundations of computing runs into severe problems right at the start. The difficulty is that although we all use words such as ‘name’, ‘value’, ‘program’, ‘expression’ or ‘command’ which we think we understand, it often turns out on closer investigation that in point of fact we all mean different things by these words, so that communication is at best precarious."
Rather than debating the semantics of the colloquial usage of referential transparency, I'm more interested in the question: what can I tell at the call site of a function without knowing the definition of the function? In an impure language, I cannot tell whether the call has side effects without looking at the definition. This is true whether I am using a macro or simply a regular function call.
Now, even if my language of choice is impure, referential transparency of expressions is still a useful concept that can inform how I write my program. I can use naming, for example, to suggest whether a function call may have side effects even if the language compiler can't verify the property. Not perfect, but better than nothing. And if I'm really confused by a bug, I can always just assume that the name is misleading and the function may have unintentional side effects. In other words, I can use the concept of referential transparency to implement a metaprogramming system in my head.
But even pedantry can't argue that Java is a fundamentally more referentially transparent language than Haskell lol. That threw me for a loop.
[1] I can point at most Java code and prove how it fails the definition. It's not especially hand-wavey.