了解GHCi让绑定的不同行为

我一直在玩Simon Marlow关于Haskell中并行和并发编程的书中的一些例子,并偶然发现了一个我不太了解的有趣的行为。 这实际上是关于我想了解GHC的一些内部工作。

假设我在REPL中执行以下操作:

λ» let x = 1 + 2 :: Int λ» let z = (x,x) λ» :sprint x x = _ λ» :sprint z z = (_,_) λ» seq x () () λ» :sprint z z = (3,3) 

好吧,这几乎是我所期望的,除了z已经被评估为WHNF。 让我们写一个类似的程序,并把它放在一个文件中:

 module Thunk where import Debug.Trace x :: Int x = trace "add" $ 1 + 2 z :: (Int,Int) z = (x,x) 

在GHCi中摆弄它:

 λ» :sprint x x = _ λ» :sprint z z = _ λ» seq x () add () λ» :sprint z z = _ λ» seq z () () λ» z (3,3) 

所以这个行为有点不同: z不提前评估WHNF。 我的问题是:

为什么z在执行let z = (x,x)时在REPL中评估为WHNF,但是在从文件中加载定义时不会执行let z = (x,x) 。 我的怀疑是它与模式绑定有关,但我不知道该在哪里寻求澄清(也许我完全是完全错误的)。 我会期望它在某种程度上像文件中的例子。

任何指针或简短的解释,为什么发生这种情况?

因为(,)是一个构造函数,所以它与Haskell的语义没有任何区别( :sprint允许访问内部的thunk实现细节,因此不计算在内)。所以这是一个GHC在编译时做什么优化和折衷的问题(x,x)在不同的位置。 其他人可能知道这些情况下的确切原因。