重叠实例-如何规避它们

时间:2018-11-18 13:37:44

标签: haskell

我是Haskell的初学者,所以请放纵自己。由于此处的原因并不重要,我试图定义一个操作符<^>,该操作符接受一个函数和一个参数,并通过参数返回该函数的值,而与该函数和该参数中的哪一个无关首先。简而言之,我希望能够编写以下内容:

foo :: Int -> Int
foo x = x * x

arg :: Int
arg = 2

foo <^> arg -- valid, returns 4
arg <^> foo -- valid, returns 4

我试图通过类型族来实现这一点,如下:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeFamilies, TypeOperators #-}

class Combine t1 t2 where
    type Output t1 t2 :: *
    (<^>) :: t1 -> t2 -> Output t1 t2

instance Combine (a->b) a where
    type Output (a->b) a = b
    f <^> x = f x

instance Combine a (a->b) where
    type Output a a->b = b
    x <^> f = f x

在此代码上,GHC抛出Conflicting family instance declarations。我的猜测是,当类型a->b和类型a相同时,GHC会抱怨重叠。我不太了解Haskell,但我怀疑使用递归类型定义可能会构造这种情况。我有几个问题:

  • 由于这是一个相当远程的场景,在我的应用程序中将永远不会发生(特别是上面的 foo arg 不会),我想知道是否有办法指定在重叠情况下要使用的虚拟默认实例?我尝试了不同的OVERLAPSOVERLAPPING标志,但是它们没有任何作用。
  • 如果没有,是否有更好的方法来实现我想要的?

谢谢!

2 个答案:

答案 0 :(得分:1)

在我看来,这是个坏主意,但我会坚持下去。

可能的解决方案是切换到功能依赖项。通常,我倾向于避免使用类型家族的眼底,但是这里它们使实例以简单的方式进行编译。

class Combine t1 t2 r | t1 t2 -> r where
    (<^>) :: t1 -> t2 -> r

instance Combine (a->b) a b where
    f <^> x = f x

instance Combine a (a->b) b where
    x <^> f = f x

请注意,如果我们使用多态函数,则此类可能在类型推断期间引起问题。这是因为,使用多态函数,代码很容易变得模棱两可。

例如,id <^> id可以选择两个实例中的任何一个。上面,Melpomene已经报告const <^> id也存在歧义。


以下内容关系不大,但我还是想分享一下:

那类型家庭呢?我尝试了一下,但发现了一个我不知道的限制。考虑封闭型家族

type family Output a b where
   Output (a->b) a = b
   Output a (a->b) = b

上面的代码可以编译,但是类型Output a (a->b)被卡住了。第二个方程式没有应用,好像第一个方程式可能匹配。

通常,我可以在其他一些情况下理解这一点,但是在这里统一

Output (a' -> b') b' ~ Output a (a -> b)

似乎失败了,因为我们需要有限类型的a ~ (a' -> b') ~ (a' -> a -> b)这是不可能的。由于某些原因,GHC不使用该参数(它假装此检查中存在无限类型吗?为什么?)

无论如何,这似乎使得用类型家庭替换眼底肌变得更加困难。我不知道为什么GHC会接受我发布的眼底代码,而拒绝OP的代码,除了使用类型族以外,基本上是同一回事。

答案 1 :(得分:1)

@chi很近;可以使用FunDeps或封闭型系列的方法。但是Combine实例与CTF Output公式一样具有潜在的模棱两可/统一性。

当chi表示接受FunDep代码时,这只有一半是正确的:GHC平原会带您沿着花园的小路走。它将接受实例,但随后您发现您无法使用它们/您收到奇怪的错误消息。请参阅“潜在的重叠”用户指南。

如果您想解决潜在的歧义Combine约束,则可能会收到一条错误消息,建议您尝试IncoherentInstances(或INCOHERENT编译指示)。不要那样做您确实有一个不连贯的问题;所有要做的就是将问题推迟到其他地方。始终可以避免使用Incoherent -只要您可以重新设置实例(如下所示)并且它们没有被锁定在库中即可。

请注意,由于存在潜在的歧义,另一个Haskell编译器(Hugs)不允许您这样编写Combine。它可以更正确地实施Haskell的规则(陈述不完善)。

答案是使用一种重叠,其中一个实例严格更加具体。首先,您必须确定在模棱两可的情况下希望使用哪种方式。我将选择以参数开头的函数:

{-# LANGUAGE UndecidableInstances, TypeFamilies #-}

instance {-# OVERLAPPING #-} (r ~ b)
         => Combine (a->b) a r  where ...

instance {-# OVERLAPPABLE #-} (Combine2 t1 t2 r)
         => Combine t1 t2 r  where
  (<^>) = revApp

class Combine2 t1 t2 r  | t1 t2 -> r  where
  revApp :: t1 -> t2 -> r

instance (b ~ r) => Combine2 a (a->b) r  where
  revApp x f = f x

请注意,OVERLAPPABLE的{​​{1}}实例几乎没有tyvar,它是一个包罗万象的东西,因此总是可匹配的。编译器要做的就是确定某些所需约束是否为Combine实例的形式。

OVERLAPPING实例上的Combine2约束不小于头部,因此您需要OVERLAPPABLE。另外要注意,推迟到UndecidableInstances意味着如果编译器仍然无法解决问题,您很可能会收到令人费解的错误消息。

谈论裸露的tyvars /“总是可匹配的”,我使用了一个额外的技巧来使编译器非常努力地改进类型:实例的开头有裸露的Combine2,具有Equality类型改善约束r。要使用(b ~ r) =>,即使您没有编写任何类型族,也需要打开~

CTF方法将是相似的。您需要在TypeFamilies上使用一个包罗万象的公式来调用辅助类型函数。同样,您需要Output