Haskell 弱头范式

due*_*ert 9 haskell functional-programming reduction ghci weak-head-normal-form

我被一些烦人的事情绊倒了。我知道 haskell 与弱头部范式 (WHNF) 一起工作,我知道这是什么。将以下代码输入 ghci(我正在使用命令 :sprint 将表达式简化为 WHNF,据我所知。):

let intlist = [[1,2],[2,3]]
:sprint intlist
Run Code Online (Sandbox Code Playgroud)

intlist = _这使得完全意义的我。

let stringlist = ["hi","there"]
:sprint stringlist 
Run Code Online (Sandbox Code Playgroud)

stringlist = [_,_] 这个已经让我困惑。但是之后:

let charlist = [['h','i'], ['t','h','e','r','e']]
:sprint charlist
Run Code Online (Sandbox Code Playgroud)

出人意料地给出 charlist = ["hi","there"]

据我了解 Haskell,字符串只不过是字符列表,这似乎是通过检查类型"hi" :: [Char]['h','i'] :: [Char].

我很困惑,因为根据我的理解,上面的所有三个示例或多或少都相同(列表列表),因此应该减少到相同的 WHNF,即 _。我错过了什么?

谢谢

K. *_*uhr 5

请注意,:sprint不会将表达式简化为 WHNF。如果是这样,那么以下将给出4而不是_

Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _
Run Code Online (Sandbox Code Playgroud)

相反,:sprint取绑定的名称,遍历绑定值的内部表示,并显示已经“评估的部分”(即构造函数的部分),同时_用作未评估的thunk 的占位符(即,暂停的惰性函数)电话)。如果该值完全未评估,则不会进行评估,甚至不会对 WHNF 进行评估。(如果该值被完全评估,你会得到它,而不仅仅是 WHNF。)

您在实验中观察到的是多态与单态数字类型的组合、字符串文字与显式字符列表的不同内部表示等。基本上,您正在观察不同文字表达式如何编译为字节码的技术差异。因此,将这些实现细节解释为与 WHNF 有关系会让您感到绝望。通常,您应该:sprint仅将其用作调试工具,而不是了解 WHNF 和 Haskell 评估语义的一种方式。

如果你真的想了解:sprint正在做什么,你可以在 GHCi 中打开一些标志来查看表达式实际上是如何处理的,因此最终编译为字节码:

> :set -ddump-simpl -dsuppress-all -dsuppress-uniques
Run Code Online (Sandbox Code Playgroud)

在此之后,我们可以看到您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>)
     [])
Run Code Online (Sandbox Code Playgroud)

可以忽略returnIO和外层:调用,专注于以((\ @ a $dNum -> ...

$dNumNum约束的字典。这意味着生成的代码尚未解析 typea中的实际类型Num a => [[a]],因此整个表达式仍表示为采用(字典)适当Num类型的函数调用。换句话说,这是一个未经评估的 thunk,我们得到:

> :sprint intlist
_
Run Code Online (Sandbox Code Playgroud)

另一方面,指定类型为Int,代码完全不同:

> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
  (: ((: (: (I# 1#) (: (I# 2#) []))
         (: (: (I# 2#) (: (I# 3#) [])) []))
      `cast` <Co:6>)
     [])
Run Code Online (Sandbox Code Playgroud)

:sprint输出也是如此:

> :sprint intlist
intlist = [[1,2],[2,3]]
Run Code Online (Sandbox Code Playgroud)

同样,文字字符串和显式字符列表具有完全不同的表示形式:

> 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>)
     [])
Run Code Online (Sandbox Code Playgroud)

并且:sprint输出中的差异代表了 GHCi 认为已评估(显式:构造函数)与未评估(unpackCString#thunk)的表达式部分的工件。