Tru*_* Do 6 haskell lazy-evaluation ghc
和1都是"hello"正常形式,因此不是 thunk。那么为什么 GHCi 显示这"hello"是一个 thunk?
ghci> x = 1::Int
ghci> :sprint x
x = 1
ghci> x = "hello"::String
ghci> :sprint x
x = _
Run Code Online (Sandbox Code Playgroud)
编辑:我复制了上一篇文章中试图解释这个问题的评论
Haskell 语言(或标准)并没有真正定义什么值是 thunk、什么是正常形式等。它定义的只是您可能对这些值调用的函数的严格语义。在你的例子中,这种情况不会发生,所以基本上编译器可以自由地做任何事情,可能涉及启发式,对于这样那样的类型,thunk 可能除了更高的内存开销之外什么也实现不了。
谁能给我一些关于这方面的文章或文件?
如果您告诉 GHC 将 的脱糖打印"hello"到 GHC Core,您可以看到字符串文字被脱糖为对 的调用unpackCString#。字符串的实际内容被紧凑地存储"hello"#,并且函数根据需要unpackCString#构造相应的链表'h' : 'e' : 'l' : 'l' : 'o' : [],因此以流式方式消耗时将占用很少的内存。
> :set -ddump-ds -dsuppress-all
> x = "hello"
==================== Desugared ====================
letrec {
x_aCa = letrec { x_aCe = unpackCString# "hello"#; } in x_aCe; } in
returnIO (: (unsafeCoerce# x_aCa) [])
Run Code Online (Sandbox Code Playgroud)
正如我之前评论的,这主要是关于 GHCi 如何决定存储值,而不是 Haskell 标准指定的任何内容。thunks/lazyness 是如何实现非严格性的实现细节,有时它们也是一个很好的心理模型,但并非总是如此。严格性在于函数以及如何允许或不允许传播 \xe2\x8a\xa5 值。任何关于 thunk 可能出现或不出现的地方的智慧大多是其副产品。
\n但在纯值/重击级别上仍然应该记住一些事情。
\n多态值总是按需评估;它们基本上是带有不可见参数的函数。在 GHCi(默认情况下关闭单态性限制)中,很容易在没有意义的情况下定义多态值,特别是数字文字。那么就:sprint只会永远给予_。
但这在您的示例中不是问题:显式限制为具体类型可确保单态绑定。
只要您直接使用普通构造函数,编译器/解释器就很有可能直接以正常形式存储它们。例如,它会针对诸如 之类的内容执行此操作(\'a\', (Just \'b\',\'c\'))。但我不认为这是有保证的,而且通常它是无关紧要的,因为文字构造函数只能让你到目前为止。所保证的是定义为外部构造函数的值可以被评估为 WHNF,而不会遇到 \xe2\x8a\xa5,但这里我们再次处于严格属性的领域。
更常见的是,您绑定的值来自某个函数,因此以 thunk 形式开始。对于许多看起来像普通构造函数但实际上并非如此的表达式来说,情况尤其如此:Haskell 中的大多数文字都可以是智能文字,无论是默认情况下还是通过扩展。众所周知,数字文字被隐式包装在 中fromInteger,以便对任何Num类型都具有多态性。因此,这确实有点令人惊讶
x = 1::Int\nRun Code Online (Sandbox Code Playgroud)\n似乎没有产生任何重击声。但再说一遍:这取决于编译器,而且它是有道理的:不存在 \xe2\x8a\xa5 的风险,该值就在那里,准备好存储,而围绕它构建一个 thunk 将需要完全不必要的指针间接。
\n值得注意的是,并非所有数字文字都会发生这种情况:
\nghci> let y = 1678796789876876987698768798769876986780987609876 :: Integer\nghci> :sprint y\ny = _\nRun Code Online (Sandbox Code Playgroud)\n在这种情况下,该数字不再适合 int 64 位,因此直接将其放入内存是否有利这一点不太明确;事实证明,在这种情况下,GHC 不会冒险。
\n现在,对于字符串,如果处于活动状态,我们将遇到大致相同的情况-XOverloadedStrings,在这种情况下,所有字符串文字都需要包装在fromString. 但反过来也不能保证:扩展不活跃并不意味着 GHC 不能将文字包装在其他函数中。正如夏立耀所示,它将其包装在低级函数中unpackCString#。(从内存的角度来看,这当然是有道理的,因为 C 字符串在内存中是连续且高效的,而 Haskell 的链表字符串坦率地说对于存储来说非常糟糕,占用了大约 16\xc3\x97 的内存。)只要编译器确定在评估字符串时不会遇到 \xe2\x8a\xa5 ,就合法。
在这种情况下,使用普通构造函数显式定义字符串确实会导致它立即以正常形式存储:
\nghci> let x = "hello" :: String\nghci> :sprint x\nx = _\nghci> let x\' = \'h\':\'e\':\'l\':\'l\':\'o\':[]\nghci> :sprint x\'\nx\' = "hello"\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
142 次 |
| 最近记录: |