I've been playing with some examples from Simon Marlow's book about parallel and concurrent programming in Haskell and stumbled across an interesting behavior that I don't really understand. This is really about me trying to understand some of the inner workings of GHC.
Let's say I do the following in the REPL:
λ» let x = 1 + 2 :: Int
λ» let z = (x,x)
λ» :sprint x
x = _
λ» :sprint z
z = (_,_)
λ» seq x ()
()
λ» :sprint z
z = (3,3)
Ok, this is pretty much what I expected except that z gets evaluated to WHNF already. Let's write a similar program and put it in a file:
module Thunk where
import Debug.Trace
x :: Int
x = trace "add" $ 1 + 2
z :: (Int,Int)
z = (x,x)
And fiddle around with it in GHCi:
λ» :sprint x
x = _
λ» :sprint z
z = _
λ» seq x ()
add
()
λ» :sprint z
z = _
λ» seq z ()
()
λ» z
(3,3)
So this behaves a little different: z
is not evaluated to WHNF in advance. My question is:
Why is z
evaluated to WHNF in the REPL when doing let z = (x,x)
but not when loading the definition from a file. My suspicion is that
it has something to do with pattern binding but I don't know where to look that up for clarification (maybe I'm just completely utterly wrong). I would have expected it to somehow behave like the example in the file.
Any pointers or a brief explanation why this happens?
Because (,)
is a constructor, the difference makes no difference to Haskell's semantics (:sprint
gives access to internal thunk implementation details so doesn't count.) So this is a question of which optimizations and trade-offs GHC does when compiling (x,x)
in different positions. Someone else may know the precise reason in these cases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With