Haskell 当范围内有多个词典时,GHC会选择哪一个词典?
考虑以下示例:Haskell 当范围内有多个词典时,GHC会选择哪一个词典?,haskell,ghc,Haskell,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的约束中使用字典,也可以使用运行时Dict获取字典。有关字典对应于不同实例的示例,请参见 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
的约束中使用字典,也可以使用运行时Dict
获取字典。有关字典对应于不同实例的示例,请参见
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碰巧使用了纳入范围的“最后一本”词典。不过,我不相信这一点
如果您仅限于重叠实例,那么您将无法为同一类型引入两个不同的字典(据我所见),并且一切都应该很好,因为字典的选择变得无关紧要
然而,不一致实例是另一个怪兽,因为它们允许您提交到泛型实例,然后在具有更具体实例的类型上使用它。这使得很难理解将使用哪个实例
简言之,不连贯的例子是邪恶的
更新:我运行了一些进一步的测试。在单独的模块中仅使用重叠实例和孤立实例,仍然可以为同一类型获取两个不同的词典。因此,我们需要更多的警告-(GHC只选择一个,这是正确的选择。同一约束的任何两个字典都应该相等 重叠实例和不连贯性在破坏力上基本上是等效的;它们都会因设计而失去实例的连贯性(程序中的任何两个相等约束都会被同一个字典所满足)RealPasePosits使您有更多的能力来计算哪些实例将被逐个地使用,但当您将DICT作为第一类值传递的时候,这不是有用的。当我考虑重叠实例时,我只考虑使用重叠实例。(例如,对于像Int这样的特定类型,它是一个更有效但在其他方面是平等的实现),但即使如此,如果我足够关心性能,以至于编写了那个专门的实现,如果它在可能的时候没有被使用,这不是一个性能缺陷吗 简言之,如果使用重叠实例,您将放弃询问将在此处选择哪个词典的权利 现在,您确实可以在不重叠实例的情况下打破实例一致性。事实上,您可以在没有孤立项和FlexibleInstances以外的任何扩展的情况下做到这一点(可以说,问题是启用FlexibleInstances时,“孤立项”的定义是错误的)。这是一个长期存在的GHC错误,部分原因尚未修复,因为(a)据任何人所知,它实际上不会直接导致崩溃,(b)可能有很多程序实际上依赖于在程序的不同部分中为同一约束提供多个实例,这可能很难避免 回到主题,原则上,GHC可以选择任何可用的字典来满足约束,这一点很重要,因为即使它们应该是相等的,GHC也可能比其他字典有更多关于其中一些字典的静态信息。您的示例有点太简单,无法进行说明,但假设您向
bar
添加一个参数;一般来说,GHC不知道通过Dict
传入的字典的任何信息,因此它必须将此视为对未知函数的调用,但您在特定类型t
处调用了foo
,该类型的作用域中有bar t
实例,那么GHC就会知道bar
来自Bar a
约束字典是T
的Bar
,可以生成对已知函数的调用,并可能内联T
的Bar
,并因此进行更多优化
实际上,GHC目前并没有这么聪明,它只使用最里面的字典。最好总是使用最外面的字典。但是像这样有多个可用字典的情况并不常见,所以我们没有好的基准测试。你能详细说明一下如何使用c吗我给出了一个片段,展示了如何在作用域中获得两个不同的实例。从这里开始,这并不难——只要在代码中假设所有实例都相等,你就有了一个问题。我认为这不会导致崩溃,除非你将破坏模块中的不变量导致的崩溃计算在内它假设实例是一致的,并使用此假设来证明
不安全性
或诸如此类的合理性(但我想说崩溃是由不安全性
引起的)具体地说,人们可能会认为,虽然混淆True
和False
对其他有效的Haskell程序是无害的,但是可以使用关联的类型来减少Int
和Maybe Int
之间无害的混淆。但事实上,GHC似乎总是检查类型族实例的重叠。@DanielWagner Int我认为禁止为同一类型导入定义两个实例的两个模块。可能在使用时会推迟检查,而