我是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,但我怀疑使用递归类型定义可能会构造这种情况。我有几个问题:
OVERLAPS
和OVERLAPPING
标志,但是它们没有任何作用。谢谢!
答案 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
。