具有类约束类型的值实际上会在运行时成为函数吗?
Posted
技术标签:
【中文标题】具有类约束类型的值实际上会在运行时成为函数吗?【英文标题】:Will a value that has a type with class constraints actually be a function at run time? 【发布时间】:2011-12-01 08:53:37 【问题描述】:考虑著名的
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
假设,为了避免单态限制,注释为:
fibs :: Num a => [a]
这似乎暗示在运行时,列表值fibs
并不真正存在,而是一个函数,每次选择fibs
的元素时都会重新计算列表?
问题是在您所知道的不同 Haskell 实现中如何实际处理此类情况。
--- 已添加 ---- 我觉得我必须详细说明一下。考虑:
fibsInteger :: [Integer]
fibsInteger = 0: 1: zipWith (+) fibsInteger (tail fibsInteger)
并假设在程序执行期间该值
(fibsInteger !! 42)
需要评估。在那种情况下,我希望随后的评估会发现 fibsInteger
的前 43 个元素已经被评估。这也意味着fibsInteger
本身和它的前 42 个尾部已经在 WHNF 中。
据我所知,使用多态 fibs
是不可能的。 FUZxxl的话
因为类型类通常会引入一个包含 具有该类型类功能的字典
似乎支持我的观点,即像 fibs
这样的值在运行时有效地显示为函数?
如果是这样,那么像 ((maximum . map (fibs!!)) [100000 .. 101000] :: Integer)
这样的应用程序需要比非多态变体 ((maximum . map (fibsInteger!!)) [100000 .. 101000] :: Integer)
明显更长的时间来评估,因为每次都必须重新计算前 100000 个数字。
(很遗憾,我目前无法尝试)
【问题讨论】:
我相信会有人来详细解释,但是你想在这里谷歌的术语是“thunk”。 @MatrixFrog - 不幸的是,不,这不会有太大帮助 - thunk 用于非严格语言的实现对我来说并不是什么新闻。关键是当我们限制@987654333@ 时,列表中的thunk 可以实际更新,例如(fibs !! 42)
被评估。我的问题是,当fibs
是多态的时,是否以及如何做到这一点。
"由于重载的数字文字,它的类型为fibs :: Num a => [a]
"。实际上由于单态限制和类型默认,它将具有类型[Integer]
。
@sepp2k - 是的,没错。但是假设类型注释是明确给出的。我会相应地编辑问题。
尾部与 fibs 的返回相匹配,因此会发生共享,您会得到 O(n) 行为。
【参考方案1】:
这取决于实现。在 GHC 中,类型类是使用 dictionaries 实现的。假设Num
类是这样定义的(在本例中进行了简化):
class Num a where
fromInteger :: Integer -> a
(+) :: a -> a -> a
然后它会被编译为“字典”数据类型:
data Num a = Num fromInteger :: Integer -> a, plus :: a -> a -> a
任何带有Num
约束的东西都将获得字典的额外参数,例如foo x = x + 1
将变为:
foo :: Num a -> a -> a
foo num x = plus num x (fromInteger num 1)
那么让我们看看 GHC 是如何编译 fibs
的吧?
$ cat Fibs.hs
module Fibs where
fibs :: Num a => [a]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
$ ghc -c Fibs.hs -ddump-simpl
==================== Tidy Core ====================
Rec
Fibs.fibs [Occ=LoopBreaker]
:: forall a_abu. GHC.Num.Num a_abu => [a_abu]
[GblId, Arity=1]
Fibs.fibs =
\ (@ a_akv) ($dNum_akw :: GHC.Num.Num a_akv) ->
GHC.Types.:
@ a_akv
(GHC.Num.fromInteger
@ a_akv $dNum_akw (GHC.Integer.smallInteger 0))
(GHC.Types.:
@ a_akv
(GHC.Num.fromInteger
@ a_akv $dNum_akw (GHC.Integer.smallInteger 1))
(GHC.List.zipWith
@ a_akv
@ a_akv
@ a_akv
(GHC.Num.+ @ a_akv $dNum_akw)
(Fibs.fibs @ a_akv $dNum_akw)
(GHC.List.tail @ a_akv (Fibs.fibs @ a_akv $dNum_akw))))
end Rec
如果你眯起眼睛,这本质上是
fibs :: Num a -> [a]
fibs num = fromInteger num 0
: fromInteger num 1
: zipWith (plus num) (fibs num) (tail (fibs num))
所以对于 GHC,答案是肯定的。正如您所怀疑的那样,这可能会对性能产生巨大影响,因为这会破坏此定义所依赖的fibs
的共享,以至于您获得指数运行时间而不是线性运行时间1。
Prelude Fibs> :set +s
Prelude Fibs> fibs !! 30
832040
(3.78 secs, 912789096 bytes)
我们可以通过引入共享来解决这个问题:
module SharedFibs where
fibs :: Num a => [a]
fibs = let f = 0 : 1 : zipWith (+) f (tail f) in f
这样好多了。
Prelude SharedFibs> :set +s
Prelude SharedFibs> fibs !! 30
832040
(0.06 secs, 18432472 bytes)
Prelude SharedFibs> fibs !! 100000
<huge number>
(2.19 secs, 688490584 bytes)
但它仍然存在fibs
在单独的调用之间不共享的相同问题。如果你想要这个,你必须将fibs
专门化为let
或where
中所需的号码类型。
这些性能上的惊喜是可怕的monomorphism restriction 存在的部分原因。
1 忽略Integer
加法不是常数时间这一事实。
【讨论】:
【参考方案2】:多态性会带来额外的性能负担(我认为这是您要问的问题)。在 Thomas 对 this question 的回答中,使非多态类型的运行时间从 36 秒减少到 11 秒。
您的陈述:
这似乎暗示在运行时,列表值 fibs 并不真正存在,而是一个函数,每次选取 fibs 的元素时都会重新计算列表?
我不太确定你在这里的意思 - 你似乎意识到它是懒惰的。您可能会问 Haskell 是否认为这是“函数声明”或“值声明” - 您可以尝试使用 Template Haskell:
> runQ [d| fib = 0 : 1 : zipWith (+) fib (tail fib) |]
[ValD (VarP fib) ...
所以它是一个值声明(ValD)。
【讨论】:
【参考方案3】:函数总是涉及(->)
类型的构造函数,所以它不是函数。这是一个价值。函数也是值,但值不是函数,这与惰性无关。函数的关键属性是您可以应用它。应用程序的类型:
(a -> b) -> a -> b
当然,它是一个惰性值,并且在实现级别涉及称为 thunk 的东西,但这与您的问题在很大程度上无关。 Thunks 是一个实现细节。仅仅因为它是一个延迟计算的值并不会将它变成一个函数。不要将评估与执行混淆! C 语言中的函数与 Haskell 中的函数不同。 Haskell 使用函数的真正数学概念,这与在机器级别执行事物的策略完全无关。
【讨论】:
换一种说法,你根本不知道它是如何实际实现的。不过,感谢您的参与。【参考方案4】:首先,列表是无限的,因此在程序运行之前不可能生成整个列表。正如 MatrixFrog 已经指出的那样,fibs
是一个 thunk。您可以粗略地将 thunk 想象为一个不带参数并返回值的函数。唯一的区别是,指向函数的指针被替换为指向结果的指针,导致结果被缓存。这只发生在不依赖于任何类型类的函数的情况下,因为类型类通常会引入一个新参数,其中包含具有该类型类功能的字典(此过程有时称为reification)。
很长一段时间以来,我发布了对这个 codegolf.SE question 的答案,其中包含 C 中的 thunk 的 own implementation。代码不是很好,列表内容与 thunk 本身没有很好的分离,但值得拥有看。
【讨论】:
这似乎间接支持了我的观点,请参阅编辑后的问题。以上是关于具有类约束类型的值实际上会在运行时成为函数吗?的主要内容,如果未能解决你的问题,请参考以下文章