GHC选择何种字典时,多于一个字典?
考虑下面的例子:
import Data.Constraint
class Bar a where
bar :: a -> a
foo :: (Bar a) => Dict (Bar a) -> a -> a
foo Dict = bar
在foo
选择Bar
实例时,GHC有两种选择:它可以使用Bar a
的字典或foo
上Bar a
约束,也可以使用运行时Dict
获取字典。 请参阅此问题以查看字典对应于不同实例的示例。
GHC使用哪种字典?为什么它是“正确的”选择?
GHC只挑选一个,这是正确的选择。 任何两个相同约束的字典应该是相同的。
重叠物质和不连贯物质在破坏力方面基本相当; 他们都通过设计失去了实例一致性(您的程序中的任何两个相同的约束都被相同的字典所满足)。 OverlappingInstances为您提供了更多的能力来确定哪些实例将在个案基础上使用,但是当您将Dicts作为第一类值传递时,这没什么用处。 我只考虑使用OverlappingInstances,当我考虑重叠的实例延伸等价的时候(例如,对于像Int这样的特定类型,更高效但平等的实现),但即使如此,如果我足够关心性能来编写专门的实现,如果它没有被使用,它会成为性能错误吗?
总之,如果你使用OverlappingInstances,你放弃了在这里选择哪个字典的问题。
现在确实可以在没有OverlappingInstances的情况下打破实例一致性。 事实上,你可以在没有孤儿的情况下做任何事情,除了FlexibleInstances以外没有任何扩展(可以说当问题是启用了FlexibleInstances时,“孤儿”的定义是错误的)。 这是一个非常长久的GHC错误,这个错误并没有得到部分解决,因为(a)它实际上不会像任何人似乎知道的那样直接导致崩溃,并且(b)可能有很多程序实际上依赖于在程序的不同部分有相同约束的多个实例,而这可能很难避免。
回到主题,原则上重要的是GHC可以选择任何可用于满足约束条件的字典,因为尽管它们应该是相同的,但GHC可能比其他字典有更多的静态信息。 你的例子有点过于简单,不足以说明,但想象一下你通过了一个参数bar
; 一般GHC不知道任何有关通过Dict
传入的Dict
因此它必须将此视为对未知函数的调用,但是您在特定类型T
调用foo
(对于其中存在一个Bar T
实例),然后GHC会知道bar
从Bar a
约束字典是T
的bar
,并可能产生一个已知函数的调用,并有可能内嵌T
的bar
,做更多的优化结果。
在实践中,GHC目前不是这么聪明,它只是使用最内层的字典。 总是使用最外面的字典可能会更好。 但是,像这样的情况下,有多个字典可用不是很常见,所以我们没有良好的基准测试。
它只是选择一个。 这不是正确的选择; 这是一个相当知名的疣。 这样会导致崩溃,所以这是一个非常糟糕的事态。 下面是一个简短的例子,它只使用GADTs
来证明一次可以有两个不同的实例:
-- file Class.hs
{-# LANGUAGE GADTs #-}
module Class where
data Dict a where
Dict :: C a => Dict a
class C a where
test :: a -> Bool
-- file A.hs
module A where
import Class
instance C Int where
test _ = True
v :: Dict Int
v = Dict
-- file B.hs
module B where
import Class
instance C Int where
test _ = False
f :: Dict Int -> Bool
f Dict = test (0 :: Int)
-- file Main.hs
import TestA
import TestB
main = print (f v)
你会发现Main.hs
编译得很好,甚至可以运行。 它使用GHC 7.10.1在我的机器上打印True
,但这不是一个稳定的结果。 将此转化为崩溃是给读者的。
这是一个测试:
{-# LANGUAGE FlexibleInstances, OverlappingInstances, IncoherentInstances #-}
import Data.Constraint
class C a where foo :: a -> String
instance C [a] where foo _ = "[a]"
instance C [()] where foo _ = "[()]"
aDict :: Dict (C [a])
aDict = Dict
bDict :: Dict (C [()])
bDict = Dict
bar1 :: String
bar1 = case (bDict, aDict :: Dict (C [()])) of
(Dict,Dict) -> foo [()] -- output: "[a]"
bar2 :: String
bar2 = case (aDict :: Dict (C [()]), bDict) of
(Dict,Dict) -> foo [()] -- output: "[()]"
上面的GHC恰好使用了被引入范围的“最后”字典。 不过,我不会依赖这个。
如果仅限于重叠的实例,那么您将无法为同一类型引入两个不同的字典(据我所知),并且一切都应该没问题,因为字典的选择变得不重要。
然而,不连贯的实例是另一个野兽,因为它们允许你提交一个通用实例,然后在具有更具体实例的类型中使用它。 这使得很难理解哪个实例将被使用。
总之,不连贯的事例是邪恶的。
更新:我跑了一些进一步的测试。 在单独的模块中只使用重叠的实例和孤立实例,您仍然可以为同一类型获取两个不同的字典。 所以,我们需要更多的警告。 :-(
链接地址: http://www.djcxy.com/p/43261.html上一篇: Which dictionary does GHC choose when more than one is in scope?