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

时间:2016-10-18 15:16:30

标签: haskell constraints ghc

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

data Sup t = ...
           | Sub t 
           deriving Eq

因为从Sup中省略的部分包含许多构造函数,其中没有一个使用t,我想派生 Eq (Sup t)而不是给出一个手动实例。

类型约束Eq t现已在(==) Sup 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忽略丢失的类型约束? 或者,有没有一种方法可以定义SupsupEq来获得所需的结果:supEq的定义,而不必在supEq的任何地方传播冗余类型约束使用和不必为Eq (Sup t)提供手动实例。

4 个答案:

答案 0 :(得分:4)

最简单的事情可能是定义自定义>>> from statistics import mean >>> mean([1, 2, 3, 4, 4]) 2.8 >>> mean([49.99, 20, 155.20, 71.65, 91.07]) 77.582 实例。

Eq (Sup t)

或者,如果您希望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 t

当然,最后一个选择是颠覆类型系统。这几乎肯定是错误的,但我会把这个决心留给你。

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

答案 1 :(得分:3)

如果您在坚持使用Eq的同时坚持不出意外的派生实例,则无法摆脱(==)约束。此外,就supEq而言,您的不变量未被强制执行(考虑如果您犯了错误并在True中交换FalseisSub)会发生什么。你可能最好只用supEq模式匹配来编写Sup

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

答案 2 :(得分:3)

当然,最简单的解决方案是将您的类型分为两种类型,正如其他人所建议的那样。但这会产生语法噪音 - 一个额外的构造者级别。如果您想要两者兼顾,可以使用reflection

import Data.Reflection
import Data.Proxy 
import Data.Coerce 

data Sup t = Sub t | A | B | C | D | E -- .. etc
  deriving (Eq, Show) 

我们现在将Eq生成的Sup代码用于(==),但在将SubSub进行比较时,请替换为其他函数,而不是reflection

首先你需要一些设置(我认为这应该在Monoid包本身 - 它有Applicativenewtype ReifiedEq a = ReifiedEq { eq :: a -> a -> Bool } newtype ReflectedEq s a = ReflectedEq a instance Reifies s (ReifiedEq a) => Eq (ReflectedEq s a) where (==) = coerce (eq (reflect (Proxy :: Proxy s))) 的similair代码:

ReflectedEq s a

这是解决方案的核心 - 类型a的值只是Reifies,但相等时使用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)来比较Sub内的h值。需要s函数来正确指定幻像类型参数(supEq = supEqWith (\_ _ -> False) ),否则它是不明确的。

您想要的功能很简单:

"String index out of range: 53"

答案 3 :(得分:3)

如果您的Sup类型可以是t中的仿函数(deriving Functor可以使用您已经给出的示例),并且您知道其他构造函数都不会使用t,那么你可以fmap (const ())

然后,您可以保证t不会有趣地影响平等检查,并且不需要原始tEq 。当两个输入都是Sub _时,您只需要保护大小写,这样就可以返回false而不是true。

subEq (Sub _) _ = False
subEq _ (Sub _) = False
subEq x y = fmap (const ()) x == fmap (const ()) y

即使您不想让Sup成为仿函数,您仍然可以实现submap :: (a -> b) ->Sup a -> Sup b并使用它。