Rot*_*sor 10 haskell ghc data-sharing monomorphism-restriction
当我偶然发现这种奇怪的行为时,我试图回答关于多态性与共享的另一个问题.
在GHCi中,当我明确定义一个多态常量时,它没有得到任何共享,这是可以理解的:
> let fib :: Num a => [a]; fib = 1 : 1 : zipWith (+) fib (tail fib)
> fib !! 30
1346269
(5.63 secs, 604992600 bytes)
Run Code Online (Sandbox Code Playgroud)
另一方面,如果我试图通过省略类型签名并禁用单态限制来实现相同的目标,那么我的常量会突然被分享!
> :set -XNoMonomorphismRestriction
> let fib = 1 : 1 : zipWith (+) fib (tail fib)
> :t fib
fib :: Num a => [a]
> fib !! 50
20365011074
(0.00 secs, 2110136 bytes)
Run Code Online (Sandbox Code Playgroud)
为什么?!
呃...当使用优化编译时,即使禁用单态限制也很快.
Vit*_*tus 11
通过提供显式类型签名,可以防止GHC对您的代码做出某些假设.我将展示一个例子(取自这个问题):
foo (x:y:_) = x == y
foo [_] = foo []
foo [] = False
Run Code Online (Sandbox Code Playgroud)
根据GHCi的说法,这个功能的类型Eq a => [a] -> Bool正如您所期望的那样.但是,如果foo使用此签名声明,则会出现"模糊类型变量"错误.
此函数仅在没有类型签名的情况下工作的原因是因为类型检查在GHC中的工作原理.省略类型签名时,foo假定[a] -> Bool某些固定类型具有单一类型a.键入绑定组后,可以对类型进行概括.这就是你得到的地方forall a. ....
另一方面,当你声明一个多态类型签名时,你明确声明它foo是多态的(因此类型[]不必与第一个参数的类型匹配)和繁荣,你得到一个模糊的类型变量.
现在,知道这一点,让我们比较核心:
fib = 0:1:zipWith (+) fib (tail fib)
-----
fib :: forall a. Num a => [a]
[GblId, Arity=1]
fib =
\ (@ a) ($dNum :: Num a) ->
letrec {
fib1 [Occ=LoopBreaker] :: [a]
[LclId]
fib1 =
break<3>()
: @ a
(fromInteger @ a $dNum (__integer 0))
(break<2>()
: @ a
(fromInteger @ a $dNum (__integer 1))
(break<1>()
zipWith
@ a @ a @ a (+ @ a $dNum) fib1 (break<0>() tail @ a fib1))); } in
fib1
Run Code Online (Sandbox Code Playgroud)
而对于第二个:
fib :: Num a => [a]
fib = 0:1:zipWith (+) fib (tail fib)
-----
Rec {
fib [Occ=LoopBreaker] :: forall a. Num a => [a]
[GblId, Arity=1]
fib =
\ (@ a) ($dNum :: Num a) ->
break<3>()
: @ a
(fromInteger @ a $dNum (__integer 0))
(break<2>()
: @ a
(fromInteger @ a $dNum (__integer 1))
(break<1>()
zipWith
@ a
@ a
@ a
(+ @ a $dNum)
(fib @ a $dNum)
(break<0>() tail @ a (fib @ a $dNum))))
end Rec }
Run Code Online (Sandbox Code Playgroud)
使用显式类型签名,foo如上所述,GHC必须将其fib视为具有潜在多态性的递归值.我们可以将一些不同的Num字典传入fib,zipWith (+) fib ...并且此时我们必须抛弃大部分列表,因为不同Num意味着不同(+).当然,一旦你使用优化进行编译,GHC会注意到Num字典在"递归调用"期间永远不会改变并将其优化掉.
在上面的核心中,您可以看到GHC确实一次又一次地提供fib了一个Num字典(命名$dNum).
因为fib在完成整个绑定组的泛化之前没有类型签名被假定为单态,所以fib子部分被赋予与整体完全相同的类型fib.多亏了这个,fib看起来像:
{-# LANGUAGE ScopedTypeVariables #-}
fib :: forall a. Num a => [a]
fib = fib'
where
fib' :: [a]
fib' = 0:1:zipWith (+) fib' (tail fib')
Run Code Online (Sandbox Code Playgroud)
并且因为类型保持固定,您可以只使用在开始时给出的一个字典.