我构建了一个类型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忽略丢失的类型约束?
或者,有没有一种方法可以定义Sup
或supEq
来获得所需的结果:supEq
的定义,而不必在supEq
的任何地方传播冗余类型约束使用和不必为Eq (Sup t)
提供手动实例。
答案 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
中交换False
和isSub
)会发生什么。你可能最好只用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
代码用于(==)
,但在将Sub
与Sub
进行比较时,请替换为其他函数,而不是reflection
。
首先你需要一些设置(我认为这应该在Monoid
包本身 - 它有Applicative
和newtype 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
不会有趣地影响平等检查,并且不需要原始t
来Eq
。当两个输入都是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
并使用它。