Haskells 弱头范式
Haskells Weak Head Normal Form
我偶然发现了一些恼人的事情。我知道 haskell 适用于弱头范式 (WHNF),我知道这是什么。在 ghci 中键入以下代码(据我所知,我正在使用命令 :sprint 将表达式简化为 WHNF。):
let intlist = [[1,2],[2,3]]
:sprint intlist
给出 intlist = _
这对我来说完全有意义。
let stringlist = ["hi","there"]
:sprint stringlist
给出 stringlist = [_,_]
这已经让我感到困惑。但是然后:
let charlist = [['h','i'], ['t','h','e','r','e']]
:sprint charlist
出人意料地给出charlist = ["hi","there"]
据我了解Haskell,字符串就是字符列表,这似乎可以通过检查类型 "hi" :: [Char]
和 ['h','i'] :: [Char]
.[=18= 来确认]
我很困惑,因为根据我的理解,上面的所有三个示例都或多或少相同(列表的列表),因此应该简化为相同的 WHNF,即 _。我错过了什么?
谢谢
请注意 :sprint
而不是 将表达式简化为 WHNF。如果是这样,那么以下将给出 4
而不是 _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
相反,:sprint
采用绑定的名称,遍历绑定值的内部表示,并在使用 [= 时显示已经 "evaluated parts"(即构造函数的部分) 19=] 作为未计算的 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>)
[])
可以忽略returnIO
和外面的:
调用,专注于((\ @ a $dNum -> ...
开头的部分
此处 $dNum
是 Num
约束的字典。这意味着生成的代码还没有解析类型 Num a => [[a]]
中的实际类型 a
,因此整个表达式仍然表示为一个函数调用,它采用(字典)适当的 [=30] =] 类型。换句话说,这是一个未评估的 thunk,我们得到:
> :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)的工件。
我偶然发现了一些恼人的事情。我知道 haskell 适用于弱头范式 (WHNF),我知道这是什么。在 ghci 中键入以下代码(据我所知,我正在使用命令 :sprint 将表达式简化为 WHNF。):
let intlist = [[1,2],[2,3]]
:sprint intlist
给出 intlist = _
这对我来说完全有意义。
let stringlist = ["hi","there"]
:sprint stringlist
给出 stringlist = [_,_]
这已经让我感到困惑。但是然后:
let charlist = [['h','i'], ['t','h','e','r','e']]
:sprint charlist
出人意料地给出charlist = ["hi","there"]
据我了解Haskell,字符串就是字符列表,这似乎可以通过检查类型 "hi" :: [Char]
和 ['h','i'] :: [Char]
.[=18= 来确认]
我很困惑,因为根据我的理解,上面的所有三个示例都或多或少相同(列表的列表),因此应该简化为相同的 WHNF,即 _。我错过了什么?
谢谢
请注意 :sprint
而不是 将表达式简化为 WHNF。如果是这样,那么以下将给出 4
而不是 _
:
Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
相反,:sprint
采用绑定的名称,遍历绑定值的内部表示,并在使用 [= 时显示已经 "evaluated parts"(即构造函数的部分) 19=] 作为未计算的 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>)
[])
可以忽略returnIO
和外面的:
调用,专注于((\ @ a $dNum -> ...
此处 $dNum
是 Num
约束的字典。这意味着生成的代码还没有解析类型 Num a => [[a]]
中的实际类型 a
,因此整个表达式仍然表示为一个函数调用,它采用(字典)适当的 [=30] =] 类型。换句话说,这是一个未评估的 thunk,我们得到:
> :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)的工件。