如何对类型类值进行模式匹配?

How to pattern match against a typeclass value?

假设我在Haskell、

中定义了一个类型类
class (Bool a) where
    false :: a
    true  :: a

为了为任何 Bool 定义通用 not 操作,需要对其潜在值进行模式匹配:

not :: Bool a => a → a
not true  = false
not false = true

不过,这无法编译。我怎样才能让它工作?

您不能匹配这样的类型类值。类型类抽象实际的底层类型,而模式匹配将暴露它。

但是,您仍然可以使用 ViewPatterns 扩展获得相同的行为和一些不错的语法。这个想法是包含一个函数,该函数接受您的抽象值并为您提供一个表示结构视图的具体类型的值:

class (Bool a) where
  true   :: a
  false  :: a
  asBool :: a -> Bool

现在您可以在模式匹配中使用 asBool 函数作为视图模式:

not :: Bool a => a -> a
not (asBool -> True)  = false
not (asBool -> False) = true

但是,请注意,这可能会导致 asBool 被计算两次,如果它隐藏了昂贵的计算,这可能是一个问题。

class CBool a where
    cfalse :: a
    ctrue  :: a

cfalsectrue 是类型 a 的成员,但此类型可以包含不是 cfalsectrue 的元素。例如

instance CBool Int where
    cfalse = 0
    ctrue  = 1

cnot 2 应该是什么? Nothing 是一个不错的选择。然后 cnot 0 = Just 1cnot 1 = Just 0。所以

cnot :: (CBool a, Eq a) => a -> Maybe a
cnot x | x == cfalse = Just ctrue
       | x == ctrue  = Just cfalse
       | otherwise   = Nothing

但还有另一种方法:

class CIBool a where
    citrue  :: a 'True
    cifalse :: a 'False

type family Not (b :: Bool) :: Bool where
    Not False = True
    Not True  = False

data Booley :: Bool -> * where
  Falsey :: Booley False
  Truey  :: Booley True

cinot :: CIBool a => Booley b -> a b -> a (Not b)
cinot Falsey _ = citrue
cinot Truey  _ = cifalse

这里 CIBoolBool 索引,Booley 是单例,cinot 是依赖类型。可以从 CBool:

导出 CIBool
newtype a :@ b = Tag { detag :: a }
    deriving Eq

instance CBool a => CIBool ((:@) a) where
    cifalse = Tag cfalse
    citrue  = Tag ctrue

(:@) 可以定义为类型族,因为它只是 Const,但我更愿意尽可能避免使用类型族。如果

zero :: Int :@ False
zero = cifalse

one  :: Int :@ True
one  = citrue

然后 cinot Falsey zero == onecinot Truey one == zero

但是请注意,虽然这是一个类型错误:

zero' :: Int :@ True
zero' = cifalse

我们可以这样定义zero'

zero' :: Int :@ True
zero' = Tag 0

然后是cinot Truey zero' == zero,这是没有意义的。因此 Tag 构造函数应该只在 CIBool 实例定义中提及。

使用的扩展名:{-# LANGUAGE GADTs, KindSignatures, DataKinds, PolyKinds, TypeFamilies, TypeOperators #-}.