Haskell 强制GHC忽略缺少的类型约束

Haskell 强制GHC忽略缺少的类型约束,haskell,constraints,ghc,Haskell,Constraints,Ghc,我构建了一个类型Sup,它使用构造函数子类型嵌入了另一个类型t的值 data Sup t = ... | Sub t deriving Eq 由于Sup中省略的部分包含大量构造函数,其中没有一个使用t,因此我想推导Eq(Sup t),而不是给出一个手动实例 instance (Eq t) => Eq (Sup t) where (Sub a) == (Sub b) = a == b A == A = True ... 对于Sup

我构建了一个类型
Sup
,它使用构造函数子类型嵌入了另一个类型
t
的值

data Sup t = ...
           | Sub t 
           deriving Eq
由于
Sup
中省略的部分包含大量构造函数,其中没有一个使用
t
,因此我想推导
Eq(Sup t)
,而不是给出一个手动实例

instance (Eq t) => Eq (Sup t) where
  (Sub a) == (Sub b) = a == b
  A == A = True
  ...
对于
Sup t
,类型约束
Eq t
现在位于
(==)
的实例上:

(==)::Eq t=>Sup t->Sup t->Bool

谓词
isSub::Sup t->Bool
定义如下:

isSub :: Sup t -> Bool
isSub (Sub _) = True
isSub _       = False
借助此谓词,我想定义以下运算符:

supEq :: Sup t -> Sup t -> Bool
supEq x y = not (isSub x) && not (isSub y) && x == y
GHC不接受上述定义,因为缺少类型约束
Eq t
。然而,由于延迟求值,我知道
t
类型的值之间的相等实际上从未使用过

有没有办法强迫GHC忽略缺少的类型约束?
或者,是否有一种方法可以定义
Sup
supEq
以获得期望的结果:定义
supEq
,而无需在使用
supEq
的地方传播冗余类型约束,也无需为
Eq(supt)提供手动实例

最简单的方法可能是定义一个自定义的
Eq(Sup)
实例

instance (Eq t) => Eq (Sup t) where
  (Sub a) == (Sub b) = a == b
  A == A = True
  ...
或者,如果希望
==
的行为类似于
supEq
(因此根本不需要
supEq
),则可以编写实例而不受约束:

instance Eq (Sup t) where
  (Sub a) == (Sub b) = False
  A == A = True
  ...
另一种方法是将
Sup
分为两种数据类型:

data Sup' = A | B | ... | Z deriving (Eq) -- nothing depends on `t`
data Sup t = Sub t | Sup'

supEq :: Sup t -> Sup t -> Bool
supEq (Sub _) _ = False
supEq _ (Sub _) = False
supEq a b = a == b
当然,最后一个选择是颠覆类型系统。这几乎肯定是错误的,但我将把这个决心留给你

{-# LANGUAGE ScopedTypeVariables #-}
import Data.Constraint

supEq :: forall t . Sup t -> Sup t -> Bool
supEq x y = let Dict = unsafeCoerce (Dict :: Dict ()) :: Dict (Eq t)
            in not (isSub x) && not (isSub y) && x == y

可能最简单的方法是定义一个自定义
Eq(Sup)
实例

instance (Eq t) => Eq (Sup t) where
  (Sub a) == (Sub b) = a == b
  A == A = True
  ...
或者,如果希望
==
的行为类似于
supEq
(因此根本不需要
supEq
),则可以编写实例而不受约束:

instance Eq (Sup t) where
  (Sub a) == (Sub b) = False
  A == A = True
  ...
另一种方法是将
Sup
分为两种数据类型:

data Sup' = A | B | ... | Z deriving (Eq) -- nothing depends on `t`
data Sup t = Sub t | Sup'

supEq :: Sup t -> Sup t -> Bool
supEq (Sub _) _ = False
supEq _ (Sub _) = False
supEq a b = a == b
当然,最后一个选择是颠覆类型系统。这几乎肯定是错误的,但我将把这个决心留给你

{-# LANGUAGE ScopedTypeVariables #-}
import Data.Constraint

supEq :: forall t . Sup t -> Sup t -> Bool
supEq x y = let Dict = unsafeCoerce (Dict :: Dict ()) :: Dict (Eq t)
            in not (isSub x) && not (isSub y) && x == y

如果使用
(==)
同时坚持使用不令人惊讶的派生实例,则无法摆脱
Eq
约束。此外,就
supEq
而言,您的不变量没有被强制执行(考虑如果您犯了错误并在
isSub
中交换
True
False
)。您最好只编写模式匹配方面的
supEq

data Sup t = Foo
           | Bar
           | Sub t 
           deriving Eq

supEq :: Sup t -> Sup t -> Bool
supEq (Sub _) _ = False
supEq _ (Sub _) = False
supEq Foo Foo = True
supEq Bar Bar = True
supEq _ _ = False
如果有足够多的案例使得以这种方式编写
supEq
变得烦人,您可以将非
Sub
案例拆分为单独的类型,如crockeea回答中的下一个最后一个示例所示,为了完整起见,复制如下:

data Sup' = Foo | Bar deriving (Eq)
data Sup t = Sub t | NotSub Sup' deriving (Eq)

supEq :: Sup t -> Sup t -> Bool
supEq (Sub _) _ = False
supEq _ (Sub _) = False
supEq (NotSub a) (NotSub b) = a == b

如果使用
(==)
同时坚持使用不令人惊讶的派生实例,则无法摆脱
Eq
约束。此外,就
supEq
而言,您的不变量没有被强制执行(请考虑如果您犯了错误并在
isSub
中交换了
True
False
,会发生什么情况)。您最好只编写模式匹配方面的
supEq

data Sup t = Foo
           | Bar
           | Sub t 
           deriving Eq

supEq :: Sup t -> Sup t -> Bool
supEq (Sub _) _ = False
supEq _ (Sub _) = False
supEq Foo Foo = True
supEq Bar Bar = True
supEq _ _ = False
如果有足够多的案例使得以这种方式编写
supEq
变得烦人,您可以将非
Sub
案例拆分为单独的类型,如crockeea回答中的下一个最后一个示例所示,为了完整起见,复制如下:

data Sup' = Foo | Bar deriving (Eq)
data Sup t = Sub t | NotSub Sup' deriving (Eq)

supEq :: Sup t -> Sup t -> Bool
supEq (Sub _) _ = False
supEq _ (Sub _) = False
supEq (NotSub a) (NotSub b) = a == b

当然,最简单的解决方案是按照其他人的建议,将您的类型分为两种。但这会产生语法噪音——额外的构造函数级别。如果您想两者兼得,可以使用:

现在,我们将为
Sup
使用生成的
Eq
代码,但在比较
Sub
Sub
时,将其替换为不同的函数,而不是
(==)

首先,您需要一些设置(我认为这应该在
反射
包本身中-它具有与
Monoid
Applicative
类似的代码):

这是解决方案的核心-类型为
reflectedq s a
的值只是一个
a
,但在进行相等性比较时,使用
提供的相等函数具体化了
,您可以随时指定。请注意,
reflection
包使用类型级机制来防止在同一上下文中使用多个
Eq
实例

现在,您可以编写比所需函数更通用的函数:

supEqWith :: (t -> t -> Bool) -> Sup t -> Sup t -> Bool 
supEqWith k x y = reify (ReifiedEq k) (\p -> h p x == h p y) where 
  h :: Proxy s -> Sup a -> Sup (ReflectedEq s a) 
  h _ = coerce 
此函数仅比较
Sup
值是否相等,使用指定的函数(
k
)比较
t
中的值。需要使用
h
函数来正确指定幻影类型参数(
s
),否则它是不明确的

您想要的功能很简单:

supEq = supEqWith (\_ _ -> False) 

当然,最简单的解决方案是按照其他人的建议,将您的类型分为两种。但这会产生语法噪音——额外的构造函数级别。如果您想两者兼得,可以使用:

现在,我们将为
Sup
使用生成的
Eq
代码,但在比较
Sub
Sub
时,将其替换为不同的函数,而不是
(==)

首先,您需要一些设置(我认为这应该在
反射
包本身中-它具有与
Monoid
Applicative
类似的代码):

这是解决方案的核心-类型为
的值reflectedq s a
只是一个
a
,但与e相比