Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have seen that in imperative paradigms

f(x)+f(x)

might not be the same as:

2*f(x)

But in a functional paradigm it should be the same. I have tried to implement both cases in Python and Scheme, but for me they look pretty straightforward the same.

What would be an example that could point out the difference with the given function?

share|improve this question
3  
You can, and often do, write referentially transparent functions in python. The difference is the language doesn't enforce it. –  Karl Bielefeldt Aug 24 at 16:22
2  
in C and alike: f(x++)+f(x++) might be not the same as 2*f(x++) (in C it's especially lovely when stuff like that is hidden within macros - did I broke my nose on that? you bet) –  gnat Aug 24 at 21:59
    
In my understanding, @gnat's example is why functionally-oriented languages like R employ pass-by-reference and explicitly avoid functions that modify their arguments. In R, at least, it can actually be difficult to skirt these restrictions (at least, in a stable, portable way) without digging into the language's complicated system of environments and namespaces and search paths. –  ssdecontrol Aug 25 at 1:30
1  
@ssdecontrol: Actually, when you have referential transparency, pass-by-value and pass-by-reference always yield the exact same result, so it doesn't matter which one the language uses. Functional languages are frequently specified with something akin to pass-by-value for semantic clarity, but their implementations often use pass-by-reference for performance (or even both, depending on which one is faster for the given context). –  Jörg W Mittag Aug 25 at 5:54
2  
@gnat: In particular, f(x++)+f(x++) can be absolutely anything, since it's invoking undefined behavior. But that's not really related to referential transparency - which would not help for this call, it's 'undefined' for referentially transparent functions as in sin(x++)+sin(x++), too. Could be 42, could format your hard drive, could have demons flying out of the users nose … –  Christopher Creutzig Aug 25 at 6:54

3 Answers 3

up vote 42 down vote accepted

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 language programming, 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

def foo(x):
  global counter

  counter += 1
  return x + counter

is not referentially transparent, in fact calling

foo(x) + foo(x)

and

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:

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 length function:

length "Hello"

will return 5. But if you want to find the length of a string read from the terminal, you cannot write

length (getLine)

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.

EDIT

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:

  1. getline of type IO String,
  2. the second is constructed by evaluating the function putStrLn of type String -> IO () on its argument.

More precisely, the second action is built by

  1. binding line to the value read by the first action,
  2. evaluating the pure functions length (compute length as an integer) and then show (turn the integer into a string),
  3. building the action by applying function putStrLn to the result of show.

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

because 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 IO ()).

EDIT 2

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.

share|improve this answer
6  
Can the downvoter suggest how I can improve this answer? –  Giorgio Aug 24 at 18:03
    
So how would one get the length of a string read from terminal in Haskell? –  exizt Aug 24 at 19:53
    
This is extremely pedantic, but for the sake of completeness, it's not Haskell's type system that ensures actions and pure functions don't mix; it's the fact that the language doesn't provide any impure functions that you can call directly. You can actually implement Haskell's IO type pretty easily in any language with lambdas and generics, but because anyone can call println directly, implementing IO doesn't guarantee purity; it'd merely be a convention. –  Doval Aug 25 at 14:15
    
I meant that (1) all functions are pure (of course, they are pure because the language does not provide any impure ones, even though as far as I know there are some mechanisms to bypass that), and (2) pure functions and impure actions have different types, so they cannot be mixed. BTW, what do you mean by call directly? –  Giorgio Aug 25 at 14:21
    
By "directly" I mean without using the IO type. –  Doval Aug 25 at 14:57
def f(x): return x()

from random import random
f(random) + f(random) == 2*f(random)
# => False

However, that's not what Referential Transparency means. RT means that you can replace any expression in the program with the result of evaluating that expression (or vice versa) without changing the meaning of the program.

Take, for example, the following program:

def f(): return 2

print(f() + f())
print(2)

This program is referentially transparent. I can replace one or both occurences of f() with 2 and it will still work the same:

def f(): return 2

print(2 + f())
print(2)

or

def f(): return 2

print(f() + 2)
print(2)

or

def f(): return 2

print(2 + 2)
print(f())

will all behave the same.

Well, actually, I cheated. I should be able to replace the call to print with its return value (which is no value at all) without changing the meaning of the program. However, clearly, if I just remove the two print statements, the meaning of the program will change: before, it printed something to the screen, after it doesn't. I/O isn't referentially transparent.

The simple rule of thumb is: if you can replace any expression, sub-expression or subroutine call with the return value of that expression, sub-expression or subroutine call anywhere in the program, without the program changing its meaning, then you have referential transparency. And what this means, practically speaking is that you can't have any I/O, can't have any mutable state, can't have any side-effects. In every expression, the value of the expression must depend solely on the values of the constituent parts of the expression. And in every subroutine call, the return value must depend solely on the arguments.

share|improve this answer
2  
"can't have any mutable state": Well, you can have it if it is hidden and does not influence the observable behaviour of your code. Think e.g. about memoization. –  Giorgio Aug 24 at 16:25
2  
@Giorgio: This is perhaps subjective, but I'd argue that cached results are not really "mutable state" if they're hidden and have no observable effects. Immutability is always an abstraction implemented on top of mutable hardware; frequently it's provided by the language (giving the abstraction of "a value" even if the value can move between registers and memory locations during execution, and can vanish once it's known it will never be used again), but it's no less valid when it's provided by a library or whatnot. (Assuming it's implemented correctly, of course.) –  ruakh Aug 24 at 23:04
    
+1 I really like the print example. Perhaps one way to see this, is that what's printed on the screen is part of the "return value". If you can replace print with its function return value and the equivalent writing on the terminal, the example works. –  Pierre Arlaud Aug 25 at 8:33
    
@ruakh: I 100% agree with you if you consider cached results that are used internally by the language runtime and are totally invisible to the programmer. On the other hand, if you implement memoization yourself (e.g. in Python) using some private mutable variable, I am not so sure. But, as you say, it may be subjective. –  Giorgio Aug 25 at 9:32
    
@ruakh It's the semantics of the language that matter, not the hardware. The memory backing an immutable value may be mutable, but neither programmer nor the compiler has to worry about the value changing. I think you're playing too fast and loose with the definitions; if you have a mutable value at the language level, you're dealing with mutable state, even if the rest of the program doesn't have to worry about it. –  Doval Aug 25 at 16:39

Parts of this answer are taken directly from an unfinished tutorial on functional programming, hosted on my GitHub account:

A function is said to be referentially transparent if it, given the same input parameters, always produces the same output (return value). If one is looking for a raison d'être for pure functional programming, referential transparency is a good candidate. When reasoning with formulae in algebra, arithmetic, and logic, this property — also called substitutivity of equals for equals — is so fundamentally important that it is usually taken for granted...

Consider a simple example:

x = 42

In a pure functional language, the left-hand and right-hand side of the equals sign are substitutable for each other both ways. That is, unlike in a language like C, the above notation truly asserts an equality. A consequence of this is that we can reason about program code just like mathematical equations.

From Haskell wiki:

Pure computations yield the same value each time they are invoked. This property is called referential transparency and makes possible to conduct equational reasoning on the code...

To contrast this, the type of operation performed by C-like languages is sometimes referred to as a destructive assignment.

The term pure is often used to describe a property of expressions, relevant to this discussion. For a function to be considered pure,

  • it is not allowed to exhibit any side effects, and
  • it must be referentially transparent.

According to the black-box metaphor, found in numerous mathematical textbooks, a function's internals are completely sealed off from the outside world. A side-effect is when a function or expression violates this principle — that is, the procedure is allowed to communicate in some way with other program units (e.g. to share and exchange information).

In summary, referential transparency is a must for functions to behave like true, mathematical functions also in the semantics of programming languages.

share|improve this answer
    
this seems to open with word-by-word copy taken from here: "A function is said to be referentially transparent if it, given the same input parameters, always produces the same output..." Stack Exchange has rules for plagiarism, are you aware about these? "Plagiarism is the soulless act of copying chunks of someone else's work, slapping your name on it and passing yourself of as the original author..." –  gnat Aug 24 at 19:15
    
I wrote that page. –  yesthisisuser Aug 24 at 19:21
    
if this is the case, consider making it look less of a plagiarism - because readers have no way to tell. Do you know how to do this at SE? 1) You refer the originals source, like "As (I have) written [here](link to source)..." followed by 2) proper quote formatting (use quote marks, or better yet, > symbol for that). It also wouldn't hurt if besides giving general guidance, answer addresses concrete question asked about, in this case about f(x)+f(x) / 2*f(x), see How to Answer - otherwise it may look like you're simply advertising your page –  gnat Aug 24 at 19:24

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.