(将评论中的有用信息提升为答案。)
独立声明与类内声明
声明 a 的两种语法不同的方式类型家族 and/or 数据族,它们在语义上是等价的:
独立:
type family Foo
data family Bar
或作为类型类的一部分:
class C where
type Foo
data Bar
两者都声明了一个类型族,但在类型类中family
部分是由class
上下文,因此 GHC/Haskell 缩写了该声明。
“新类型”与“类型同义词”/“类型别名”
data family F
创建一个新类型,类似于data F = ...
创建一个新类型。
type family F
不创建新类型,类似于type F = Bar Baz
不会创建新类型(它只是创建现有类型的别名/同义词)。
非内射性的例子type family
一个例子(稍作修改)来自Data.MonoTraversable.Element https://hackage.haskell.org/package/mono-traversable-0.2.0.0/docs/Data-MonoTraversable.html:
import Data.ByteString as S
import Data.ByteString.Lazy as L
-- Declare a family of type synonyms, called `Element`
-- `Element` has kind `* -> *`; it takes one parameter, which we call `container`
type family Element container
-- ByteString is a container for Word8, so...
-- The Element of a `S.ByteString` is a `Word8`
type instance Element S.ByteString = Word8
-- and the Element of a `L.ByteString` is also `Word8`
type instance Element L.ByteString = Word8
在类型族中,方程的右侧Word8
命名现有类型;左侧的事物创建了新的同义词:Element S.ByteString
and Element L.ByteString
拥有同义词意味着我们可以互换Element Data.ByteString
with Word8
:
-- `w` is a Word8....
>let w = 0::Word8
-- ... and also an `Element L.ByteString`
>:t w :: Element L.ByteString
w :: Element L.ByteString :: Word8
-- ... and also an `Element S.ByteString`
>:t w :: Element S.ByteString
w :: Element S.ByteString :: Word8
-- but not an `Int`
>:t w :: Int
Couldn't match expected type `Int' with actual type `Word8'
这些类型同义词是“非内射的”(“单向”),因此是不可逆的。
-- As before, `Word8` matches `Element L.ByteString` ...
>(0::Word8)::(Element L.ByteString)
-- .. but GHC can't infer which `a` is the `Element` of (`L.ByteString` or `S.ByteString` ?):
>(w)::(Element a)
Couldn't match expected type `Element a'
with actual type `Element a0'
NB: `Element' is a type function, and may not be injective
The type variable `a0' is ambiguous
更糟糕的是,GHC 甚至无法解决明确的情况!:
type instance Element Int = Bool
> True::(Element a)
> NB: `Element' is a type function, and may not be injective
注意使用“可能不是”!我认为 GHC 很保守,拒绝检查是否Element
确实是内射的。 (也许是因为程序员could加上另一个type instance
后来,在导入预编译模块后,增加了歧义。
单射性的例子data family
相反:在数据族中,每个右侧都包含一个唯一的构造函数 ,因此定义是单射(“可逆”)方程。
-- Declare a list-like data family
data family XList a
-- Declare a list-like instance for Char
data instance XList Char = XCons Char (XList Char) | XNil
-- Declare a number-like instance for ()
data instance XList () = XListUnit Int
-- ERROR: "Multiple declarations of `XListUnit'"
data instance XList () = XListUnit Bool
-- (Note: GHCI accepts this; the new declaration just replaces the previous one.)
With data family
,看到右侧的构造函数名称(XCons
, or XListUnit
)
足以让类型推断器知道我们必须使用XList ()
not an XList Char
。由于构造函数名称是唯一的,因此这些定义are单射/可逆。
If type
“just”声明了一个同义词,为什么它在语义上有用?
通常,type
同义词只是缩写,但是type
家族同义词具有额外的力量:它们可以创建一个简单的类型(kind*
) 成为“type with kind”的同义词* -> *
应用于论证”:
type instance F A = B
makes B
match F a
。例如,这用于Data.MonoTraversable
制作一个简单的类型Word8
匹配类型的函数Element a -> a
(Element
定义如上)。
例如,(有点傻),假设我们有一个版本const
仅适用于“相关”类型:
> class Const a where constE :: (Element a) -> a -> (Element a)
> instance Const S.ByteString where constE = const
> constE (0::Word8) undefined
ERROR: Couldn't match expected type `Word8' with actual type `Element a0'
-- By providing a match `a = S.ByteString`, `Word8` matches `(Element S.ByteString)`
> constE (0::Word8) (undefined::S.ByteString)
0
-- impossible, since `Char` cannot match `Element a` under our current definitions.
> constE 'x' undefined