重叠的多参数实例和实例特异性


{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
module OverlappingSpecificsError where
class EqM a b where
(===) :: a -> b -> Bool
instance {-# OVERLAPPABLE #-} Eq a => EqM a a where
a === b = a == b
instance {-# OVERLAPPABLE #-} EqM a b where
a === b = False
aretheyreallyeq :: (Eq a, Eq b) => Either a b -> Either a b -> Bool
aretheyreallyeq (Left a1) (Right b2) = a1 == b2
aretheyeq :: (Eq a, Eq b) => Either a b -> Either a b -> Bool
aretheyeq (Left a1) (Right b2) = a1 === b2

aretheyreallyeqaretheyeq都没有编译,但aretheyreallyeq的错误对我来说是有意义的,而且告诉我aretheyeq不应该给出错误:由于aretheyreallyeq上的相同错误,GHCi认为aretheyeqEqM可能出现的实例之一应该是不可能的。怎么回事?

重点是,GHCi坚持EqM的两个实例都适用于aretheyeq。但a1a型,b2b型,因此为了使第一个实例适用,必须将ab型统一起来。

但这应该是不可能的,因为它们在函数签名时被声明为类型变量(也就是说,使用第一个EqM实例会导致函数类型为Either a a -> Either a a -> Bool,而aretheyreallyeq中的错误告诉我GHCi不允许这样做(这正是我所期望的)。

我是遗漏了什么,还是这是如何检查具有多参数类型类的重叠实例的错误?

我想这可能与ab稍后可以进一步实例化到它们相等的点有关,在aretheyeq之外,然后第一个实例是否有效?但CCD_ 22也是如此。唯一的区别是,如果它们不统一,我们可以选择aretheyeq,但我们不能选择aretheyreallyeq。在任何情况下,Haskell都没有动态调度,原因有很多,那么,无论以后的ab是否一致,提交一个总是工作的实例有什么担心呢?也许有某种方法可以表达这一点,从而使在调用函数时选择实例成为可能?

值得注意的是,如果我删除了第二个实例,那么函数显然仍然没有编译,说明找不到实例EqM a b。所以,如果我没有这个例子,那么没有一个有效,但当这个有效时,突然另一个也有效,我有重叠?几英里外的我闻起来像虫子。

泛型变量上的实例匹配以这种方式工作,以防止出现一些潜在的混淆(和危险)场景。

如果编译器屈服于你的直觉,在编译aretheyeq时选择了EqM a b实例(因为正如你所说,ab不一定统一),那么下面的调用:

x = aretheyeq (Left 'z') (Right 'z')

会返回False,这与直觉相反。

Q:等一下!但在这种情况下,a ~ Charb ~ Char,我们还有Eq aEq b,这意味着Eq Char,这应该可以选择EqM a a实例,不是吗?

嗯,是的,我想理论上这可能会发生,但Haskell就是不这样做。类实例只是传递给函数(作为方法字典)的额外参数,因此为了有一个实例,它必须在函数本身中明确可选择,必须从使用者传入。

前者(明确可选择的实例)必然要求只有一个实例。事实上,如果删除EqM a a实例,则函数会编译并始终返回False

后者(从使用者传递实例)意味着对函数的约束,如下所示:

aretheyeq :: EqM a b => Either a b -> Either a b -> Bool

您要问的是,Haskell本质上有两个不同版本的此函数:一个需要a ~ b并选择EqM a a实例,另一个不需要,并选择EqM a b实例。

然后编译器会聪明地选择"正确"的版本。因此,如果我调用aretheyeq (Left 'z') (Right 'z'),就会调用第一个版本,但如果我调用aretheyeq (Left 'z') (Right 42),就会调用第二个版本。

但现在进一步思考:如果有两个版本的aretheyeq,选择哪一个取决于类型是否相等,那么考虑一下:

dummy :: a -> b -> Bool
dummy a b = aretheyeq (Left a) (Right b)

dummy如何知道要选择哪个版本的aretheyeq?所以现在也必须有两个版本的dummy:一个用于a ~ b,另一个用于其他情况。

等等。连锁反应一直持续到出现具体的类型。

Q:等一下!为什么有两个版本?难道就不能只有一个版本,然后根据传入的参数来决定该做什么吗?

啊,但不行!这是因为类型在编译时被擦除。当函数开始运行时,它已经被编译,并且没有更多的类型信息。因此,一切都必须在编译时决定:选择哪个实例,以及从中产生的连锁反应

从某种意义上说,它并不是一个完全按照文档工作的bug。从开始

现在假设,在某个客户端模块中,我们正在搜索目标约束(C ty1 .. tyn)的实例。搜索工作方式如下:

查找候选实例的第一阶段如您所愿;EqM a b是唯一的候选者,因此是主要候选者。但最后一步是

现在查找所有与目标约束统一但不匹配的实例,或范围给定的约束中的实例。当目标约束进一步实例化时,这些非候选实例可能会匹配。如果它们都是不连贯的顶级实例,则搜索成功,返回主要候选实例。否则搜索将失败。

EqM a a实例属于这一类,并且不是不连贯的,因此搜索失败。您可以通过将其标记为{-# INCOHERENT #-}而不是overappable来实现您想要的行为。

为了进一步完成Alexey的回答,这确实给了我应该做些什么来实现我想要的行为的提示,我编译了以下一个与我的真实用例(与ExistentialQuantification有关)略有不同的情况的最低工作示例:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ExistentialQuantification #-}
module ExistentialTest where
class Class1 a b where
foo :: a -> b
instance {-# INCOHERENT #-} Monoid a => Class1 a (Either b a) where
foo x = Right (x <> x)
instance {-# INCOHERENT #-} Monoid a => Class1 a (Either a b) where
foo x = Left x
data Bar a = Dir a | forall b. Class1 b a => FromB b
getA :: Bar a -> a
getA (Dir a) = a
getA (FromB b) = foo b
createBar :: Bar (Either String t)
createBar = FromB "abc"
createBar2 :: Bar (Either t String)
createBar2 = FromB "def"

如果删除{-# INCOHERENT #-}注释,那么在createBarcreateBar2上会出现与我原来的示例完全相同的编译错误,这一点是相同的:在createBar中,很明显唯一合适的实例是第二个,而在createBar2中,唯一合适的是第一个,但Haskell拒绝编译,因为它在使用时可能会造成明显的混乱,直到您用INCOHERENT对它们进行注释。

然后,代码的工作方式完全符合您的预期:getA createBar返回Left "abc",而getA createBar2返回Right "defdef",这正是在合理类型系统中可能发生的唯一情况。

因此,我的结论是:INCOHERENT注释正是为了允许我从一开始就想做的事情,而不会让Haskell抱怨可能令人困惑的实例,并且确实采取了唯一有意义的实例。对于INCOHERENT是否可以使用任意的实例进行编译(这显然是糟糕和危险的),仍然存在疑问,即即使在考虑了所有内容之后,也确实保持重叠的实例。因此,我的结论的一个推论是:只有在绝对需要并且绝对确信确实只有一个有效实例的情况下,才使用INCOHERENT

我仍然认为有点荒谬的是,Haskell没有更自然、更安全的方法来告诉编译器不要再担心我可能会感到困惑,不要再做显然是问题唯一类型检查答案的事情。。。

aretheyreallyeq失败,因为作用域中有两个不同的类型变量。在中

aretheyreallyeq :: (Eq a, Eq b) => Either a b -> Either a b -> Bool
aretheyreallyeq (Left a1) (Right b2) = a1 == b2

a1 :: ab2 :: b,没有比较潜在不同类型的值的方法(因为这是它们的声明方式),所以这失败了。当然,这与任何已启用的扩展或杂注无关。

aretheyeq失败是因为有两个实例可以匹配,而不是它们肯定匹配。我不确定你使用的是什么版本的GHC,但我看到的异常消息似乎很清楚:

• Overlapping instances for EqM a b arising from a use of ‘===’
Matching instances:
instance [overlappable] EqM a b -- Defined at /home/tmp.hs:12:31
instance [overlappable] Eq a => EqM a a
-- Defined at /home/tmp.hs:9:31
(The choice depends on the instantiation of ‘a, b’
To pick the first instance above, use IncoherentInstances
when compiling the other instance declarations)
• In the expression: a1 === b2
In an equation for ‘aretheyeq’:
aretheyeq (Left a1) (Right b2) = a1 === b2

在这种情况下,我的解释是,它是说,给定a和b的某些选择,可能会有多个不同的匹配实例。

相关内容

  • 没有找到相关文章

最新更新