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相比