请注意,:sprint
这不会将表达式简化为 WHNF。如果是这样,那么以下将给出4
而不是_
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
相反,:sprint
采用绑定的名称,遍历绑定值的内部表示,并显示已经“评估的部分”(即构造函数的部分),同时_
用作未评估的 thunk 的占位符(即暂停的惰性函数来电)。如果该值完全未评估,则不会进行评估,甚至不会对 WHNF 进行评估。(如果该值被完全评估,您将得到它,而不仅仅是 WHNF。)
您在实验中观察到的是多态与单态数字类型、字符串文字的不同内部表示与显式字符列表等的组合。基本上,您正在观察不同文字表达式如何编译为字节码的技术差异。因此,将这些实现细节解释为与 WHNF 有关,这会让您感到困惑。通常,您应该:sprint
仅将其用作调试工具,而不是作为了解 WHNF 和 Haskell 评估语义的一种方式。
如果您真的想了解:sprint
在做什么,您可以在 GHCi 中打开一些标志来查看表达式实际上是如何处理的,因此最终编译为字节码:
> :set -ddump-simpl -dsuppress-all -dsuppress-uniques
在此之后,我们可以看到您intlist
给出的原因_
:
> let intlist = [[1,2],[2,3]]
==================== Simplified expression ====================
returnIO
(: ((\ @ a $dNum ->
: (: (fromInteger $dNum 1) (: (fromInteger $dNum 2) []))
(: (: (fromInteger $dNum 2) (: (fromInteger $dNum 3) [])) []))
`cast` <Co:10>)
[])
您可以忽略 thereturnIO
和外部:
调用,并专注于以开头的部分((\ @ a $dNum -> ...
这$dNum
是Num
约束的字典。这意味着生成的代码尚未解析 typea
中的实际类型Num a => [[a]]
,因此整个表达式仍表示为采用(字典)适当Num
类型的函数调用。换句话说,这是一个未经评估的重击,我们得到:
> :sprint intlist
_
另一方面,将类型指定为Int
,代码完全不同:
> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
(: ((: (: (I# 1#) (: (I# 2#) []))
(: (: (I# 2#) (: (I# 3#) [])) []))
`cast` <Co:6>)
[])
输出也是:sprint
:
> :sprint intlist
intlist = [[1,2],[2,3]]
类似地,文字字符串和显式字符列表具有完全不同的表示:
> let stringlist = ["hi", "there"]
==================== Simplified expression ====================
returnIO
(: ((: (unpackCString# "hi"#) (: (unpackCString# "there"#) []))
`cast` <Co:6>)
[])
> let charlist = [['h','i'], ['t','h','e','r','e']]
==================== Simplified expression ====================
returnIO
(: ((: (: (C# 'h'#) (: (C# 'i'#) []))
(: (: (C# 't'#)
(: (C# 'h'#) (: (C# 'e'#) (: (C# 'r'#) (: (C# 'e'#) [])))))
[]))
`cast` <Co:6>)
[])
并且输出中的差异:sprint
表示 GHCi 认为表达式的哪些部分已评估(显式:
构造函数)与未评估( unpackCString#
thunk)的工件。