1

If Haskell is lazy, why does getLine is evaluated in both of the following cases? Being lazy I would expect that in the case of fa the getLine would not be evaluated because its result is not being used subsequently:

let fa = do { x <- getLine; return "hello" } let fb = do { x <- getLine; return $ x } 

(I tested both cases in GHCi)

Thanks

0

3 Answers 3

4

Its result is being used, just not in the way you necessarily expect. This de-sugars to

fa = getLine >>= (\x -> return "hello") 

So the result of getLine is still passed to the function \x -> return "hello". Monads are inherently about sequencing actions together (among other things); that sequencing still occurs even if results are later not used. If that weren't the case, then

main = do print "Hello" print "World" 

wouldn't do anything as a program, since the results of both calls to print aren't being used.

Sign up to request clarification or add additional context in comments.

7 Comments

Not to put too fine a point on it, but the point here is that x (and not getLine!) is the thing which lazy evaluation allows you to avoid evaluating. You can verify this by executing do { x <- return (error "oh, I guess x was evaluated after all"); putStrLn "nah, x wasn't evaluated" } in ghci. (You can know x isn't evaluated because you can see it isn't mentioned inside the lambda body; but you can't know getLine isn't evaluated unless you are privy to the implementation of >>=.)
@DanielWagner So, is there some internal definition for >>= in the IO monad that forces evaluation of the actions in both sides? I tried to find this implementation but couldn't. Where can I see it?
@mljrg There is indeed. Beware: deep magic!
@DanielWagner Ok, I know it's using the State monad, but I suspect that the ccall construct is always executed, irrespective if its value is used or not in a subsequent action. Or .. or ... that (World, a) that every IO action returns and consumes is forcing all IO actions that involve side-effects to be executed irrespective if their values a are being subsequently used or not (Just guessing)
@mljrg It isn't using the standard State monad, but it is using something similar. You are correct that there's a piece in the runtime system that has to cooperate with the apparently (but appearances are deceptive) pure-Haskell implementation; this is commonly described by separating "execution" from "evaluation", as I think you are doing.
|
3

Congratulations, you've just discovered why Haskell is a pure functional language!

The result of getLine is not a string. It's an IO action which happens to “produce” a string. That string is indeed not evaluated, but the action itself is (since it turns up in a do block bound to main), and this is all that matters as far as side-effects are concerned.


Really just the value of getLine. This is not a function, so it doesn't actually have a result.

1 Comment

This answer is incomplete; see that of @MathematicalOrchid.
2

Be careful now... ;-)

The result of getLine isn't a string, it's an "I/O command object", if you will. The code actually desugars to

getLine >>= (\ x -> return "hello") 

The >>= operator constructs a new I/O command object out of an existing one and a function... OK, that's a bit much to wrap your mind around. The important thing is, the I/O action gets executed (because of the implementation of >>= for IO), but its result doesn't necessarily get evaluated (because of laziness).

So let's look at the implementation of the IO monad... erm, actually you know what? Let's not. (It's deep magic, hard-wired into the compiler, and as such it's implementation-specific.) But this phenomenon isn't unique to IO by any means. Let's look at the Maybe monad:

instance Monad Maybe where mx >>= f = case mx of Nothing -> Nothing Just x -> f x return x = Just x 

So if I do something like

do x <- foobar return "hello" 

Will x get evaluated? Let's look. It desugars to:

foobar >>= (\ x -> return "hello") 

then this becomes

case foobar of Nothing -> Nothing Just x -> Just "hello" 

As you can see, foobar is clearly going to be evaluated, because we need to know whether the result is Nothing or Just. But the actual x won't be evaluated, because nothing looks at it.

It's kind of the same way that length evaluates the list nodes, but not the list elements they point to.

6 Comments

Thanks for your answer, but I have two questions to make. First, when you desugar foobar >>= (\ x -> return "hello") it desugars to ... Just x -> return "hello" isn't it? Second, I am not afraid of "deep magic", so, can you show to me such magic behind the implementation of >>= in the IO monad, so that we can close this Stackoverflow question in a complete and useful way for others to come? Thanks.
Yes, it's Just x -> return "hello". And then, from the definition of return we get Just x -> Just "hello".
My point is, this effect is nothing to do with the magical low-level implementation of I/O (which varies between GHC versions); it's common to most monads, including Maybe.
You said "the magical low-level implementation of I/O (which varies between GHC versions)". I hope the case if for an improved implementation across successive versions, keeping the same behavior ...
The behavior of the IO monad is specified in the Haskell Report, and does not change. The implementation is not specified at all, and can be whatever GHC (or any other compiler) wants it to be.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.