IncoherentInstances如何运作?

Dan*_*ton 35 haskell typeclass ghc

一些代码:

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Arity f where
  arity :: f -> Int

instance Arity x where
  arity _ = 0

instance Arity f => Arity ((->) a f) where
  arity f = 1 + arity (f undefined)
Run Code Online (Sandbox Code Playgroud)

没有IncoherentInstances:

ghci> arity foldr
blah blah ambiguous blah blah possible fix blah
ghci> arity (foldr :: (a -> Int -> Int) -> Int -> [a] -> Int)
3
ghci> let f x y = 3 in arity f
2
ghci> arity $ \x y -> 3
2
Run Code Online (Sandbox Code Playgroud)

如果我们添加IncoherentInstances到pragma列表中,那么它可以处理foldr而不需要单态类型签名,但它在lambda上得到了错误的答案:

ghci> arity foldr
3
ghci> let f x y = 3 in arity f
2
ghci> arity $ \x y -> 3 -- should be 2
0
Run Code Online (Sandbox Code Playgroud)

Incoherent Instances背后的黑魔法是什么?它为什么要做它在这里做的事情?

Dav*_*ani 30

这很复杂.让我们从模棱两可的错误开始:

<interactive>:1:1:
    Ambiguous type variable `b0' in the constraint:
      (Arity b0) arising from a use of `arity'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: arity foldr
    In an equation for `it': it = arity foldr
Run Code Online (Sandbox Code Playgroud)

通常,在没有重叠实例的情况下,当尝试将类型与类匹配时,它会将类型与该类的所有实例进行比较.如果只有一个匹配,它将使用该实例.过度使用,您将获得无实例错误(例如,带有show (*))或重叠实例错误.例如,如果OverlappingInstances从上述程序中删除语言功能,则会出现以下错误 arity (&&):

<interactive>:1:1:
    Overlapping instances for Arity (Bool -> Bool -> Bool)
      arising from a use of `arity'
    Matching instances:
      instance Arity f => Arity (a -> f)
        -- Defined at tmp/test.hs:9:10-36
      instance Arity x -- Defined at tmp/test.hs:12:10-16
    In the expression: arity (&&)
    In an equation for `it': it = arity (&&)
Run Code Online (Sandbox Code Playgroud)

它匹配Arity (a -> f),如aBoolfBool -> Bool.它还匹配Arity x,如x可以Bool -> Bool -> Bool.

随着OverlappingInstances,当两个或多个实例可以匹配时面临的情况,如果有,将选择一个最特殊的一个.如果可以匹配,则X实例比实例更具体,但反之亦然.YXY

在这种情况下,(a -> f)匹配xx不匹配(a -> f)(例如考虑x存在Int).所以Arity (a -> f)比具体更具体Arity x,因此如果两者匹配,前者将被选中.

使用这些规则,arity (&&)将首先匹配Arity ((->) a f),a存在Boolf存在Bool -> Bool.下一场比赛将aBoolf布尔.最后它将结束Arity xxBool的匹配.


注意上面的函数,(&&)结果是具体类型Bool.但是当类型不具体时会发生什么?例如,让我们看看结果arity undefined.undefined有类型a,所以它不是具体类型:

<interactive>:1:1:
    Ambiguous type variable `f0' in the constraint:
      (Arity f0) arising from a use of `arity'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: arity undefined
    In an equation for `it': it = arity undefined
Run Code Online (Sandbox Code Playgroud)

你得到一个不明确的类型变量错误,就像foldr那样.为什么会这样?这是因为取决于什么a,将需要不同的实例.如果aInt,Arity x则应匹配实例.如果aInt -> Int,Arity ((->) a f)则应匹配实例.因此,ghc拒绝编译程序.

如果你注意到foldr:的类型foldr :: forall a b. (a -> b -> b) -> b -> [a] -> b,你会注意到同样的问题:结果不是具体的变量.


以下是其中IncoherentInstances的内容:启用该语言功能后,它将忽略上述问题,只选择一个始终与变量匹配的实例.例如arity undefined,Arity x将始终匹配a,因此结果将为0.类似的事情是为了完成foldr.


现在针对第二个问题,为什么arity $ \x y -> 3IncoherentInstaces启用时返回0 ?

这是非常奇怪的行为.以下ghci会话将显示它有多奇怪:

*Main> let f a b = 3
*Main> arity f
2
*Main> arity (\a b -> 3)
0
Run Code Online (Sandbox Code Playgroud)

这让我觉得ghc中有一个bug,\a b -> 3可以看到IncoherentInstances有类型x而不是a -> b -> Int.我想不出为什么这两个表达式不应该完全一样的原因.

  • arity(\ ab - > a + b)给出正确的结果,我认为这是优化器核化死变量的问题(分析将它们标记为至少死了),当它可能不应该时. (5认同)
  • +1感谢OverlappingInstances和IncoherentInstances的良好解释. (4认同)
  • @NathanHowell-有趣。`arity $ \ ab-&gt; const ab`产生`2`,但是`arity $ \ ab-&gt; a`产生`0`。 (2认同)
  • @NathanHowell做优化器在这里扮演任何角色,因为这是在GHCi会话中? (2认同)
  • 似乎应该从问题中删除"如何",然后呢?:)我的印象是,有很多人认为IncoherentInstances是一个坏主意,并且不会遗憾地看到它完全消失了. (2认同)