haskell - 简而言之,'type family' 与 'data family' ?

标签 haskell type-families

我对如何在数据族类型族之间进行选择感到困惑。维基页面TypeFamilies涉及很多细节。有时,它在散文中非正式地将 Haskell 的数据族称为“类型族”,但当然 Haskell 中也有类型族

有一个简单的示例,显示了两个版本的代码的显示位置,区别仅在于声明的是数据系列还是类型系列:

-- 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
这里的

typedata含义相同,但是type family版本无法进行类型检查,而data family 版本很好,因为 data family “创建新类型,因此是单射的”(维基页面说)。

我从这一切中得到的结论是“对于简单的情况尝试数据族,如果它不够强大,那么尝试类型族”。这很好,但我想更好地理解它。是否有我可以遵循的维恩图或决策树来区分何时使用哪个?

最佳答案

(将评论中的有用信息提升为答案。)

独立声明与类内声明

声明类型族和/或数据族的两种语法上不同的方法,在语义上是等效的:

独立:

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 的非注入(inject)性示例

来自 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.ByteStringElement L.ByteString

拥有同义词意味着我们可以互换 Element Data.ByteStringWord8 :

-- `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 ,看到右侧的构造函数名称( XConsXListUnit ) 足以让类型推断器知道我们必须使用 XList ()不是XList Char 。由于构造函数名称是唯一的,因此这些定义是单射/可逆的。

如果 type “just”声明了一个同义词,为什么它在语义上有用?

通常,type同义词只是缩写,但是type系列同义词增加了功能:它们可以使简单类型 (kind * ) 成为“将 kind * -> * 应用于参数的类型”的同义词:

type instance F A = B

使B匹配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 

关于haskell - 简而言之,'type family' 与 'data family' ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20870432/

相关文章:

Haskell 函数转换为类型

haskell - Haskell 的代码度量工具

haskell - 断言类型类适用于类型族应用程序的所有结果

haskell - 哪些语言扩展可以写入 "class A (B c) => D c where ..."?这个类型类声明的含义是什么?

Haskell:类型族的实例定义

haskell - 何时使用类型族进行类型级计算 "happen"?

exception - 如何在 Haskell 中抛出异常并退出程序?

haskell - 透明的错误处理

haskell - 数字作为乘法函数(奇怪但有趣)

haskell - 使用 GHCi 扩展类型同义词、类型族