在Haskell中将两个类合并/合并为一个

时间:2010-07-17 14:53:25

标签: haskell types ghc typeclass

我有两个非重叠类型的集合,并且想要制作另外两个集合的集合。 代码示例:

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 aB 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

1 个答案:

答案 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