'type family'vs'data family',简而言之?
我对如何在data family
和type family
之间进行选择感到困惑。 TypeFamilies上的wiki页面进入了很多细节。 偶尔它会非正式地将Haskell的data family
称为散文中的“类型族”,但当然Haskell中也有type family
。
有一个简单的例子,显示了两个版本的代码的显示位置,不同之处仅在于是否声明了data family
或type family
:
-- BAD: f is too ambiguous, due to non-injectivity
-- type family F a
-- OK
data family F a
f :: F a -> F a
f = undefined
g :: F Int -> F Int
g x = f x
type
和data
具有相同的含义,但type family
版本无法进行类型检查,而data family
版本没有问题,因为data family
“创建新类型并因此是内射的”(维基页面说)。
我从这一切中获得的结果是“试用简单案例的data family
,如果功能不够强大,那就试试type family
”。 这很好,但我想更好地理解它。 是否有维恩图或我可以遵循的决策树来区分何时使用哪个?
我不认为任何决策树或维恩图将存在,因为类型和数据族的应用程序非常广泛。
一般来说,您已经强调了关键设计差异,并且我会同意您的外观,以便首先了解您是否可以脱离data family
。
对我来说,关键在于data family
每个实例都会创建一个新类型,这会大大限制权力,因为您不能完成通常最自然的事情,并使现有类型成为实例。
例如,Haskell wiki页面上的“索引类型”上的GMapKey
示例对于数据族来说是非常合适的:
class GMapKey k where
data GMap k :: * -> *
empty :: GMap k v
lookup :: k -> GMap k v -> Maybe v
insert :: k -> v -> GMap k v -> GMap k v
地图k
的关键类型是数据族的参数,实际的地图类型是数据族( GMap k
)的结果。 作为GMapKey
实例的用户,您可能非常高兴GMap k
类型是抽象的,只需通过类类中的通用映射操作即可处理。
相比之下,同一维基页面上的Collects
例子则恰恰相反:
class Collects ce where
type Elem ce
empty :: ce
insert :: Elem ce -> ce -> ce
member :: Elem ce -> ce -> Bool
toList :: ce -> [Elem ce]
参数类型是集合,结果类型是集合的元素。 一般来说,用户将希望直接使用该类型的正常操作来操作这些元素。 例如,集合可能是IntSet
,元素可能是Int
。 将Int
包裹在其他类型中会相当不方便。
注意 - 这两个例子都与类型类有关,因此不需要family
关键字来声明类型类中的类型,这意味着它必须是一个族。 但是,对于独立系列,完全一样的考虑也适用,这只是抽象如何组织的问题。
(将评论中的有用信息提升为答案。)
独立与类内声明
两种语义上不同的声明类型族和/或数据族的方式,它们在语义上是等价的:
独立:
type family Foo
data family Bar
或者作为类型类的一部分:
class C where
type Foo
data Bar
都声明一个类型族,但是在一个类型class
中, class
上下文隐含了family
部分,所以GHC / Haskell缩写了这个声明。
“新类型”与“类型同义词”/“类型别名”
data family F
创建一个新类型,类似于data F = ...
创建新类型的方式。
type family F
不会创建新类型,类似于type F = Bar Baz
不会创建新类型(它只是为现有类型创建别名/同义词)。
type family
的非注入性示例
来自Data.MonoTraversable.Element
的示例(稍作修改):
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
和Element L.ByteString
具有同义词意味着我们可以将Element Data.ByteString
与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
是否真的是内射的。 (也许是因为程序员以后可能会在导入预编译模块后添加另一个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.)
对于data family
,看到右侧的构造函数名称( XCons
或XListUnit
)足以让类型推理器知道我们必须使用XList ()
而不是XList Char
。 由于构造函数名称是唯一的,所以这些定义是内射/可逆的。
如果type
“just”声明同义词,为什么它在语义上有用?
通常, type
同义词只是缩写,但type
族同义词增加了力量:它们可以使简单类型(kind *
)成为应用于参数的“类型构造函数(kind * -> *
)的同义词”:
type instance F A = B
使B
匹配F a
。 例如,在Data.MonoTraversable
来创建类型为Element a -> a
( Element
定义如上)的简单类型的Word8
匹配函数。
例如(有点愚蠢),假设我们有一个只适用于“相关”类型的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.
> constF 'x' undefined
链接地址: http://www.djcxy.com/p/78245.html