如何对类型类值进行模式匹配?
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
cfalse
和 ctrue
是类型 a
的成员,但此类型可以包含不是 cfalse
和 ctrue
的元素。例如
instance CBool Int where
cfalse = 0
ctrue = 1
cnot 2
应该是什么? Nothing
是一个不错的选择。然后 cnot 0 = Just 1
和 cnot 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
这里 CIBool
由 Bool
索引,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 == one
和 cinot 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 #-}
.
假设我在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
cfalse
和 ctrue
是类型 a
的成员,但此类型可以包含不是 cfalse
和 ctrue
的元素。例如
instance CBool Int where
cfalse = 0
ctrue = 1
cnot 2
应该是什么? Nothing
是一个不错的选择。然后 cnot 0 = Just 1
和 cnot 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
这里 CIBool
由 Bool
索引,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 == one
和 cinot 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 #-}
.