我有两个非重叠类型的集合,并且想要制作另外两个集合的集合。 代码示例:
class A a
class B b
class AB ab
instance A a => AB a
instance B b => AB b
GHC 6.12.3不允许使用错误消息声明:
Duplicate instance declarations: instance (A a) => AB a -- Defined at playground.hs:8:9-19 instance (B b) => AB b -- Defined at playground.hs:9:9-19
我理解,此声明会导致失去对AB a
重叠实例的控制,因为A a
和B b
的实例可能会在以后出现(我看不到简单易行的方法)这一点)。
我想应该有一些“解决方法”来获得相同的行为。
P.S。变种如:
newtype A a => WrapA a = WrapA a
newtype B b => WrapB b = WrapB b
instance A a => AB (WrapA a)
instance B b => AB (WrapB b)
和
data WrapAB a b = A a => WrapA a
| B b => WrapB b
instance AB (WrapAB a b)
以及包含其中某些类型的任何其他类型都不符合我的需求(通过第三方声明的类类型选择实现)
对@camccann的评论: 添加标志以控制标记上的合并/选择类型是个好主意,但我想避免像重叠实例的种类这样的事情。对于对此答案感兴趣的人,压缩变体:
data Yes
data No
class IsA a flag | a -> flag
class IsB b flag | b -> flag
instance Delay No flag => IsA a flag
instance Delay No flag => IsB b flag
instance (IsA ab isA, IsB ab isB, AB' isA isB ab) => AB ab
class AB' isA isB ab
instance (A a) => AB' Yes No a
instance (B b) => AB' No Yes b
instance (A a) => AB' Yes Yes a
class Delay a b | a -> b
instance Delay a a
instance IsA Bool Yes
instance A Bool
答案 0 :(得分:3)
据我所知,没有“好”的方法来实现这一目标。你被困在某个地方添加了一些东西。既然你不想要包装器类型,我能想到的另一个选择就是搞乱类定义,这意味着我们要进入类型元编程 - 土地。
现在,这种方法不“好”的原因是类约束基本上是不可撤销的。一旦GHC看到约束,它就会坚持下去,如果它不能满足约束,则编译失败。这适用于类实例的“交集”,但对“联合”没有帮助。
为了解决这个问题,我们需要类型谓词与类型级布尔,而不是直接类约束。为此,我们使用具有功能依赖性的多参数类型来创建类型函数,并使用具有延迟统一的重叠实例来编写“默认实例”。
首先,我们需要一些有趣的语言编译指示:
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE UndecidableInstances #-}
定义一些类型级布尔值:
data Yes = Yes deriving Show
data No = No deriving Show
class TypeBool b where bval :: b
instance TypeBool Yes where bval = Yes
instance TypeBool No where bval = No
TypeBool
课程并非绝对必要 - 我主要使用它来避免使用undefined
。
接下来,我们为我们想要结合的类型类编写成员资格谓词,默认实例用作直通案例:
class (TypeBool flag) => IsA a flag | a -> flag
class (TypeBool flag) => IsB b flag | b -> flag
instance (TypeBool flag, TypeCast flag No) => IsA a flag
instance (TypeBool flag, TypeCast flag No) => IsB b flag
TypeCast
约束当然是Oleg臭名昭着的类型统一类。它的代码可以在这个答案的最后找到。这里有必要延迟选择结果类型 - fundep说第一个参数确定第二个,默认实例是完全通用的,因此将No
直接放在实例头中将被解释为始终评估的谓词为假,这没有用。使用TypeCast
代替,直到GHC选择最具体的重叠实例,这会强制结果为No
时,并且只有在没有更多特定实例的情况下才能找到。
我将对类型类本身进行另一种非必要的调整:
class (IsA a Yes) => A a where
fA :: a -> Bool
gA :: a -> Int
class (IsB b Yes) => B b where
fB :: b -> Bool
gB :: b -> b -> String
类上下文约束确保,如果我们为一个类编写一个实例而不编写匹配的谓词实例,我们将立即得到一个神秘的错误,而不是以后很容易混淆的错误。为了演示目的,我还在课程中添加了一些函数。
接下来,union类被分成两部分。第一个只有一个通用实例,它只应用成员谓词并调用第二个,它将谓词结果映射到实际实例。
class AB ab where
fAB :: ab -> Bool
instance (IsA ab isA, IsB ab isB, AB' isA isB ab) => AB ab where
fAB = fAB' (bval :: isA) (bval :: isB)
class AB' isA isB ab where fAB' :: isA -> isB -> ab -> Bool
instance (A a) => AB' Yes No a where fAB' Yes No = fA
instance (B b) => AB' No Yes b where fAB' No Yes = fB
instance (A ab) => AB' Yes Yes ab where fAB' Yes Yes = fA
-- instance (B ab) => AB' Yes Yes ab where fAB' Yes Yes = fB
请注意,如果两个谓词都为真,我们将明确选择A
实例。注释掉的实例也是如此,但使用B
代替。您也可以删除两者,在这种情况下,您将获得两个类的独占分离。 bval
这里是我使用TypeBool
类的地方。还要注意要获取正确类型布尔值的类型签名 - 这需要我们在上面启用的ScopedTypeVariables
。
要总结一下,尝试一些实例:
instance IsA Int Yes
instance A Int where
fA = (> 0)
gA = (+ 1)
instance IsB String Yes
instance B String where
fB = not . null
gB = (++)
instance IsA Bool Yes
instance A Bool where
fA = id
gA = fromEnum
instance IsB Bool Yes
instance B Bool where
fB = not
gB x y = show (x && y)
在GHCi中尝试:
> fAB True
True
> fAB ""
False
> fAB (5 :: Int)
True
> fAB ()
No instance for (AB' No No ())
. . .
这是TypeCast
代码,由Oleg提供。
class TypeCast a b | a -> b, b->a where typeCast :: a -> b
class TypeCast' t a b | t a -> b, t b -> a where typeCast' :: t->a->b
class TypeCast'' t a b | t a -> b, t b -> a where typeCast'' :: t->a->b
instance TypeCast' () a b => TypeCast a b where typeCast x = typeCast' () x
instance TypeCast'' t a b => TypeCast' t a b where typeCast' = typeCast''
instance TypeCast'' () a a where typeCast'' _ x = x