“types家庭”与“数据家族”,简而言之?
我对如何selectdata family
和type family
感到困惑。 TypeFamilies上的wiki页面进入了很多细节。 有时候,它非正式地将Haskell的data family
称为散文中的“types家族”,但当然在Haskell中也有type family
。
有一个简单的例子,显示了两个版本的代码显示在哪里,不同的是data family
还是type family
正在声明:
-- BAD: f is too ambiguous, due to non-injectivity -- type family F a -- OK data family F af :: F a -> F a f = undefined g :: F Int -> F Int gx = fx
type
和data
具有相同的含义,但type family
版本不能进行types检查,而data family
版本是好的,因为data family
“创build新types,因此是内射的”(维基页面)。
我从这一切中得出的结论是“为简单的案例尝试data family
,如果function不够强大,那就试试type family
”。 这很好,但我想更好地理解它。 有没有一个维恩图或决策树,我可以按照区分何时使用哪个?
我不认为任何决策树或维恩图将存在,因为types和数据族的应用程序是相当广泛的。
一般来说,你已经强调了关键的devise差异,我同意你的外卖,首先看看你是否可以摆脱data family
。
对我来说关键的一点是, data family
每个实例都会创build一个新types,这大大限制了权力,因为你不能做最通常最自然的事情,而使现有types成为实例。
例如,“索引types”上的Haskell wiki页面上的GMapKey
示例对于数据族是非常合适的:
class GMapKey k where data GMap k :: * -> * empty :: GMap kv lookup :: k -> GMap kv -> Maybe v insert :: k -> v -> GMap kv -> GMap kv
地图k
的关键types是数据族的参数,实际的地图types是数据族( GMap k
)的结果。 作为GMapKey
实例的用户,您可能非常高兴GMap k
types是抽象的,只需通过类类中的通用映射操作即可操作。
相比之下,在同一个维基页面上的Collects
例子则恰恰相反:
class Collects ce where type Elem ce empty :: ce insert :: Elem ce -> ce -> ce member :: Elem ce -> ce -> Bool toList :: ce -> [Elem ce]
参数types是集合,结果types是集合的元素。 一般来说,用户将要直接使用该types的正常操作来操作这些元素。 例如,集合可能是IntSet
,元素可能是Int
。 将Int
包装在其他types中会相当不方便。
注意 – 这两个例子都是用type类的,所以不需要family
关键字来声明type类中的types,这意味着它必须是一个family。 然而,对于独立的家庭来说,完全一样的考虑也适用,这只是一个抽象如何组织的问题。
(将评论中的有用信息提升为答案)
独立与类内声明
两种语义上不同的声明types族和/或数据族的方式 ,在语义上是等价的:
独立:
type family Foo data family Bar
或者作为types类的一部分:
class C where type Foo data Bar
都声明一个types族,但是在一个types类中,这个family
一部分被class
上下文所隐含,所以GHC / Haskell缩写了这个声明。
“新types”与“types同义词”/“types别名”
data family F
创build一个新types,类似于data F = ...
创build新types的方式。
type family F
不会创build新的types,类似于type F = Bar Baz
不会创build新types(它只是为现有types创build一个别名/同义词)。
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
在一个types的家庭中,方程Word8
命名一个现有的types; 东西是左侧创build新的同义词: Element S.ByteString
和Element L.ByteString
具有同义词意味着我们可以将Element Data.ByteString
与Word8
:
-- `w` is a Word8.... >let w = 0::Word8 -- ... and also an `Element L.ByteString` >:tw :: Element L.ByteString w :: Element L.ByteString :: Word8 -- ... and also an `Element S.ByteString` >:tw :: Element S.ByteString w :: Element S.ByteString :: Word8 -- but not an `Int` >:tw :: Int Couldn't match expected type `Int' with actual type `Word8'
这些types的同义词是“非内射”(“单向”),因此是不可逆的。
-- 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
)查看构造函数名就足以让types推理器知道我们必须使用XList ()
而不是XList Char
。 由于构造函数名称是唯一的,这些定义是内射的/可逆的。
如果type
“just”声明一个同义词,为什么它在语义上是有用的?
通常, type
同义词只是缩写,但type
族同义词增加了力量:它们可以使简单types(kind *
)成为应用于参数的“types构造函数(kind * -> *
)”的同义词:
type instance FA = B
使B
匹配F a
。 例如,在Data.MonoTraversable
使用这个Data.MonoTraversable
来创buildtypes为Element a -> a
( Element
在上面定义)的简单types的Word8
匹配函数。
例如,(有点傻),假设我们有一个只适用于“related”types的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