Deep under the hood, any functional language with heavy roots in lambda calculus is compiled to code, closely following some abstract computation model. For haskell it is STG-machine, for ocaml/caml-light it was CAM-machine. This execution model usually has something like local variables and clear route of execution and source code translates into code for abstract machine by clear, well-defined algorithm. So, breakpoints can be inserted in haskell (for example) code and something like local variables can be determined, when expression with breakpoint is evaluated.
For haskell and other lazy languages, however, the problem is that it does not make much good (except profiling). REPL allows coding expressions with easy, as no side-effects goes away from function body definition, so no real need to debug functions locally and asserts will locate error for sure. The real source of bugs usually is space leak due laziness in wrong place, and completely different tools, inspecting heap structure, are needed (and some are available).
Let's begin with a definition for referential transparency:
An expression is said to be referentially transparent if it can be
replaced with its value without changing the behavior of a program (in
other words, yielding a program that has the same effects and output
on the same input).
What that means is that (for example) you can replace 2 + 5 with 7 in any part of the program, and the program should still work. This process is called substitution. Substitution is valid if, and only if, 2 + 5 can be replaced with 7 without affecting any other part of the program.
Let's say that I have a class called Baz
, with the functions Foo
and Bar
in it. For simplicity, we'll just say that Foo
and Bar
both return the value that is passed in. So Foo(2) + Bar(5) == 7
, as you would expect. Referential Transparency guarantees that you can replace the expression Foo(2) + Bar(5)
with the expression 7
anywhere in your program, and the program will still function identically.
But what if Foo
returned the value passed in, but Bar
returned the value passed in, plus the last value provided to Foo
? That's easy enough to do if you store the value of Foo
in a local variable within the Baz
class. Well, if the initial value of that local variable is 0, the expression Foo(2) + Bar(5)
will return the expected value of 7
the first time you invoke it, but it will return 9
the second time you invoke it.
This violates referential transparency two ways. First, Bar can not be counted on to return the same expression each time it is called. Second, a side-effect has occurred, namely that calling Foo influences the return value of Bar. Since you can no longer guarantee that Foo(2) + Bar(5)
will equal 7, you can no longer substitute.
This is what Referential Transparency means in practice; a referentially transparent function accepts some value, and returns some corresponding value, without affecting other code elsewhere in the program, and always returns the same output given the same input.
Best Answer
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:
is not referentially transparent, in fact calling
and
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
and any integer
x
, it is always true thatAn example of an action is the result of the library function
getLine
: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:Actions are special in that you can tell the Haskell runtime to execute them by writing:
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: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) butgetLine
is a value of typeIO String
(an action). In this way, the type system ensures that an action value likegetLine
(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 typeInt
.EDIT
To answer exizt question, here is a small Haskell program that reads a line from the console and prints its length.
The main action consists of two subactions that are executed sequentially:
getline
of typeIO String
,putStrLn
of typeString -> IO ()
on its argument.More precisely, the second action is built by
line
to the value read by the first action,length
(compute length as an integer) and thenshow
(turn the integer into a string),putStrLn
to the result ofshow
.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:because
show (length line)
has typeString
whereas the do notation requires that an action (getLine
of typeIO String
) be followed by another action (e.g.putStrLn (show (length line))
of typeIO ()
).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.