40

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?

6
  • 8
    You can, and often do, write referentially transparent functions in python. The difference is the language doesn't enforce it. Aug 24, 2014 at 16:22
  • 7
    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, 2014 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. Aug 25, 2014 at 1:30
  • 5
    @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). Aug 25, 2014 at 5:54
  • 5
    @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 … Aug 25, 2014 at 6:54

4 Answers 4

65

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

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.

Haskell, a purely functional language, strictly separates expression evaluation in which pure functions are applied and which is always referentially transparent, from action execution (processing of special values), which is not referentially transparent, i.e. executing the same action can have each time a different result.

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

As a result of expression evaluation, this function (actually a constant) first of all produces a pure value of type IO String. Values of this type are values like any other: you can pass them around, put them in data structures, compose them using special functions, and so on. For example you can make a list of actions like so:

[getLine, getLine] :: [IO String]

Actions are special in that you can tell the Haskell runtime to execute them by writing:

main = <some action>

In this case, when your Haskell program is started, the runtime walks through the action bound to main and executes it, possibly producing side-effects. Therefore, action execution is not referentially transparent because executing the same action two times can produce different results depending on what the runtime gets as input.

Thanks to Haskell's type system, an action can never be used in a context where another type 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 is a value of type IO String (an action). In this way, the type system ensures that an action value like getLine (whose execution is carried out outside the core language and which may be non-referentially transparent) cannot be hidden inside a non-action value of type Int.

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 environment (IO) caused by evaluating an expression. So, for a correct, general definition, you should refer to that answer.

16
  • 10
    Can the downvoter suggest how I can improve this answer?
    – Giorgio
    Aug 24, 2014 at 18:03
  • So how would one get the length of a string read from terminal in Haskell? Aug 24, 2014 at 19:53
  • 3
    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, 2014 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, 2014 at 14:21
  • 6
    Your point about getLine not being referentially transparent is incorrect. You are presenting getLine as if it evaluates to or reduces to some String, the particular String of which depends on the user's input. This is incorrect. IO String does not contain a String any more than Maybe String does. IO String is a recipe for maybe, possibly obtaining a String and, as an expression, it as pure as any other in Haskell.
    – LuxuryMode
    Jun 7, 2016 at 13:51
28
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.

12
  • 4
    "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, 2014 at 16:25
  • 4
    @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, 2014 at 23:04
  • 1
    +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. Aug 25, 2014 at 8:33
  • 1
    @Giorgio Space/time usage can't be considered a side effect for the purposes of referential transparency. That would make 4 and 2 + 2 non-interchangeable since they have different running times, and the whole point of referential transparency is that you can substitute an expression with whatever it evaluates to. The important consideration would be thread safety.
    – Doval
    Aug 25, 2014 at 17:05
  • 1
    @overexchange: Referential Transparency means that you can replace every subexpression with its value without changing the meaning of the program. listOfSequence.append(n) returns None, so you should be able to replace every call to listOfSequence.append(n) with None without changing the meaning of your program. Can you do that? If not, then it is not referentially transparent. Feb 9, 2015 at 13:46
1

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.

4
  • 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, 2014 at 19:15
  • 3
    I wrote that page.
    – laserpants
    Aug 24, 2014 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, 2014 at 19:24
  • 1
    Theoretically, I understood this answer. But, practically following these rules, I need to return hailstone sequence list in this program. How do I do this? Feb 9, 2015 at 15:27
0

The referential transparency is the property of a function when a function always returns same result for the same input. The referential transparency is a property possessed by a mathematical function.

e.g. f (x) = y;

Here the function,

1]Always accepts some argument. (A function without an argument is never referetially transparent).

2]Always return some value. (Here 'Y').

3]Acts only on it's inputs and not on any outside entity.

4]For a given 'x' it always returns same 'Y'.

Many answers here have explained it well with good examples. So I am not going to cover them. I just want to make the concept more clear with what I have understood.

Referential transparency is a vital thing in functional programming. Application of functional transparency results in a testable cacheable codebase.

A referentially transparent function never accesses or manipulates the global data. Thus it leads to thread safe code that does not need locking or synchronization.

Referential transparency and testable code: (Citing a JavaScript code)

var percentValue = 5;
var calculateTax = (value) => {
    return value/100 * (100 + percentValue);
}

If we consider the above code for testing the result will vary every time the value of global variable "percentValue" changes. Thus the function will not give same result for same input every time. So there is no referential transparency here. This will not lead to testable code.

Now look at this code:

var calculateTax = (value, percentValue) => {
    return value/100 * (100 + percentValue);
}

The above code is referentially transparent and thus can be tested painlessly. Thus referential transparency leads to testable code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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