Referential transparency, referred to a function, indicates that you can determine the result of applying that function only by looking at the values of its arguments. You can write referentially transparent functions in any programming language, e.g. Python, Scheme, Pascal, C.
On the other hand, in most languages you can also write non referentially transparent functions. For example, this Python function:
counter = 0
counter += 1
return x + counter
is not referentially transparent, in fact calling
foo(x) + foo(x)
2 * foo(x)
will produce different values, for any argument
x. The reason for this is that the function uses and modifies a global variable, therefore the result of each invocation depends on this changing state, and not only on the function's argument.
Some languages (e.g. purely functional languages like Haskell) strictly separate functions, which are always referentially transparent, from other constructs like actions, which need not be referentially transparent.
So, for any Haskell function
f :: Int -> Int
and any integer
x, it is always true that
2 * (f x) == (f x) + (f x)
An example of an action is the result of the library function
getLine :: IO String
This function produces an action that, when executed, reads a line of text from the terminal. Therefore, this action is not referentially transparent because its result depends on what the user types in each time.
Thanks to Haskell's type system, an action can never be used in a context
where a function call is expected, and vice versa. So, if you want to find the length of a string you can use the
will return 5. But if you want to find the length of a string read from the terminal, you cannot write
because you get a type error:
length expects an input of type list (and a String is, indeed, a list) but
getLine returns a value of type
IO String (an action). In this way, the type system ensures that non-referentially transparent code (inpure code) cannot be arbitrarily mixed with pure code.
To answer exizt question, here is a small Haskell program that reads a line from the console and prints its length.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
The main action consists of two subactions that are executed sequentially:
getline of type
- the second is constructed by evaluating the function
putStrLn of type
String -> IO () on its argument.
More precisely, the second action is built by
line to the value read by the first action,
- evaluating the pure functions
length (compute length as an integer) and then
show (turn the integer into a string),
- building the action by applying function
putStrLn to the result of
At this point, the second action can be executed. If you have typed "Hello", it will print "5".
Note that if you get a value out of an action using the
<- notation, you can only use that value inside another action, e.g. you cannot write:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
show (length line) has type
String whereas the do notation requires that an action (
getLine of type
IO String) be followed by another action (e.g.
putStrLn (show (length line)) of type
Jörg W Mittag's definition of referential transparency is more general than mine (I have upvoted his answer). I have used a restricted definition because the example in the question focuses on the return value of functions and I wanted to illustrate this aspect. However, RT in general refers to the meaning of the whole program, including changes to global state and interactions with the enviroment (IO) caused by evaluating an expression. So, for a correct, general definition, you should refer to that answer.