views:

157

answers:

3

Hello.

I was reading a Haskell tutorial (Learn You a Haskell) in which the author said that laziness goes well with referential transparency. After more reading and some searching, I still don't understand why. Note that I do understand what's great about referential transparency and laziness, but it's them together that's troubling me.

Is there any special benefit of the combination of the two?

Or maybe the author just wanted to say they're both nice to have and expressed that ambiguously?

+3  A: 

Referential transparency means that the function will always return the same output given the same input. So if does not matter if the function is lazy or strict. The lazy function will compute its output at some unknown time in future but due to referential transparency you are guaranteed that the output will be same always for the given inputs.

So in a way, referential transparency assures the correctness of lazy functions.

abhin4v
+10  A: 

It's really easy. A non-strict (e.g. lazy) evaluation means that tasks can be postponed. But in order to postpone something, you're better sure that you get later that same result as you would get now, and that's referential transparency. Consider this imperative Java code:

long start = System.currentTimeMillis(); //get the start time
runBenchmarkFunction();
System.out.println("Run took " + (System.currentTimeMillis() - start) + " ms"); 

Now a lazy language would postpone the evalutation of the first line, because start is only needed in the third line. So the result would be 0 (or very close to it). Probably that isn't what you want. The reason for that trouble would be that System.currentTimeMillis is not referential transparent. You wouldn't have any problem in that case if it were a function in the "mathematical sense" like sin or ln, which are referential transparent.

Landei
"state is only needed in the third line". This is not strictly true. The side-effect of having asked the system for the current time is needed to run the second line; it is of type `IO a`, not `a`. runBenchmarkFunction is of similar type, and in order to run both, you have to combine them with some function that ensures that actions are performed in order, `>>` in Haskell. So you're really writing `currentTime >>= \st -> runBenchmarkFunction >> currentTime >>= \end -> putStrLn "it took " ++ (show $ end - st) ++ " seconds"`. Clearly the second line depends on the first, so it all works.
jrockway
@jrockway: I'm using here a fictional language you could call "lazy Java" as an very crude and easy example to explain the connection between laziness and referential transparency. How the problem is solved in Haskell is a **completely different** question. Note that Haskell's "way of the monad" is not the only possibility, e.g. Clean uses its type system to deal with this problem.
Landei
The question is tagged "haskell". But still, what you've written is perfectly referentially transparent if you imagine the right semantics. And, if you imagine other semantics, then it's not. But the key word there is "imagine".
jrockway
+4  A: 

Consider this Python code, where a generator is used to lazily compute an infinite sequence. It has no referential transparency because of its use of global state, hence a caller of the generator can't be sure that the result they are getting has not been affected by some other event.

foo = 0

def foo_sequence():
    global foo
    while True:
        foo += 1
        yield foo

>>> generator = foo_sequence()
>>> generator.next()
1
>>> generator.next()
2
>>> foo = 5
>>> generator.next()
6

In this case, the caller would prefer to generate the whole sequence atomically, to prevent such events happening. Hence, the lack of referential transparency (in this contrived example) makes the laziness unattractive.

Ben James
Very nice example. Of course it's possible to break referential transparency in Haskell too, although there are usually warning signs along the way.
John
@John: You mean with using primitive stuff or `unsafePerformIO`? These are considered harmful if not used properly. Can't imagine another way.
FUZxxl
@FUZxxl, that's exactly what I mean, and the `unsafe` prefix would be the warning. Another way is to wrap a pointer to a ByteString, then modify the pointer.
John
@John: Note that `unsafePerformIO` (and everything else with `unsafe` in its name) is not part of the Haskell standard, so you can't actually break referential transparency in "pure" Haskell (not that that's practically relevant).
sepp2k
@sepp2k, while that may be true, it's still possible to do evil things in Haskell98. See http://www.mail-archive.com/[email protected]/msg21785.html. As I understand that thread, although you don't violate RT you do violate causality. Neither is satisfying.
John
@John: Yes, good point. I did not consider lazy IO.
sepp2k