什么时候我的Haskell表达式被评估?

oro*_*ome 5 evaluation haskell lazy-evaluation

如果我定义

?> data Bar = Bar Int deriving Show
?> data Foo = Foo Bar deriving Show
Run Code Online (Sandbox Code Playgroud)

?> let foo = trace "foo" Foo (trace "bar" Bar 100)
?> let two = trace "two" 2
?> let g (Foo x)  y = y
Run Code Online (Sandbox Code Playgroud)

然后我想我明白为什么会得到

?> g foo two
foo
two
2
Run Code Online (Sandbox Code Playgroud)

但是,如果我重复这一点,我会得到

?> g foo two
two
2
Run Code Online (Sandbox Code Playgroud)

我不明白为什么foo似乎没有被评估为第二次调用g,特别是因为它显然不是(尚)以某种方式已经可用,因为我可以验证

?> foo
Foo bar
(Bar 100)
Run Code Online (Sandbox Code Playgroud)

虽然 - 再次,我的困惑 - 重复前面给出

?> foo
Foo (Bar 100)
Run Code Online (Sandbox Code Playgroud)

为什么我的foo表达式在某些情况下似乎已经过评估,而在其他情况下却没有评估?那么为什么我的two表达总是需要被评估?

Zet*_*eta 9

这是由于two类型.让我们检查目前为止的所有类型:

ghci> :t foo
foo :: Foo
ghci> :t two
two :: Num a => a
Run Code Online (Sandbox Code Playgroud)

啊哈!two是多态的.因此,它的行为取决于实际的Num实例.因此,需要重新评估g.我们可以通过以下方式检查:sprint:

ghci> :sprint foo
foo = Foo _             -- simplified
Run Code Online (Sandbox Code Playgroud)

这表明我们从未查看过的内容Foo.foo处于弱头正常状态.这回答了你的第二个问题.但回到你的第一个.会发生什么:sprint two

ghci> :sprint two
two = _
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,由于其多态性,two无法获得WHNF.毕竟, WHNF应时间?您可能希望将其用作Integer,或Currency,或Complex Triple.

顺便说一下,这是存在单同性限制的原因,参见"Haskell的历史",第6.2节:

6.2单态限制

早期阶段争议的主要来源是所谓的"单态限制".假设genericLength有这种重载类型:

genericLength` :: Num a => [b] -> a
Run Code Online (Sandbox Code Playgroud)

现在考虑这个定义:

f xs = (len, len)
  where
    len = genericLength xs
Run Code Online (Sandbox Code Playgroud)

看起来len应该只计算一次,但它实际上可以计算两次.为什么?因为我们可以推断出类型 len :: (Num a) => a; 当与字典传递翻译相结合时,len成为每次出现时调用一次的函数,每个函数len可以在不同的类型中使用.

有关限制的更多信息,另请参阅此问答.

话虽这么说,如果我们修复two类型,我们可以很容易地改变它:

ghci> let foo = trace "foo" Foo (trace "bar" Bar 100)
ghci> let two = trace "two" (2 :: Integer)
ghci> let g (Foo x)  y = y
Run Code Online (Sandbox Code Playgroud)

现在输出将完全按照您的预期.或者,您可以启用单态限制:set -XMonomorphismRestriction,因为默认情况下在当前GHCi版本中禁用它:

ghci> :set -XMonomorphismRestriction
ghci> let two = trace "two" 2
ghci> :t two
two :: Integer -- due to defaulting rules
Run Code Online (Sandbox Code Playgroud)


chi*_*chi 5

> :t foo
foo :: Foo
> :t two
two :: Num a => a
Run Code Online (Sandbox Code Playgroud)

第二个是多态的,所以它是伪装的功能!每次使用它时,您都会评估一个新的trace呼叫.