类型族可以做什么,多参数类型 类 和功能依赖不能
What can type families do that multi param type classes and functional dependencies cannot
我玩过 TypeFamilies
、FunctionalDependencies
和 MultiParamTypeClasses
。在我看来,TypeFamilies
似乎没有在其他两个上添加任何具体功能。 (但反之则不然)。但我知道字体家族很受欢迎,所以我觉得我错过了一些东西:
"open" 类型之间的关系,例如转换函数,TypeFamilies
似乎不可能。完成 MultiParamTypeClasses
:
class Convert a b where
convert :: a -> b
instance Convert Foo Bar where
convert = foo2Bar
instance Convert Foo Baz where
convert = foo2Baz
instance Convert Bar Baz where
convert = bar2Baz
类型之间的满射关系,例如一种类型安全的伪鸭子类型机制,通常使用标准类型族来完成。完成 MultiParamTypeClasses
和 FunctionalDependencies
:
class HasLength a b | a -> b where
getLength :: a -> b
instance HasLength [a] Int where
getLength = length
instance HasLength (Set a) Int where
getLength = S.size
instance HasLength Event DateDiff where
getLength = dateDiff (start event) (end event)
类型之间的双射关系,例如对于未装箱的容器,这可以通过 TypeFamilies
与数据族来完成,尽管您必须为每个包含的类型声明一个新的数据类型,例如一个newtype
。要么是那个,要么是单射类型族,我认为在 GHC 8 之前不可用。使用 MultiParamTypeClasses
和 FunctionalDependencies
:
完成
class Unboxed a b | a -> b, b -> a where
toList :: a -> [b]
fromList :: [b] -> a
instance Unboxed FooVector Foo where
toList = fooVector2List
fromList = list2FooVector
instance Unboxed BarVector Bar where
toList = barVector2List
fromList = list2BarVector
最后是两种类型和第三种类型之间的满射关系,例如python2或java式除法函数,可以用TypeFamilies
也可以用MultiParamTypeClasses
。完成 MultiParamTypeClasses
和 FunctionalDependencies
:
class Divide a b c | a b -> c where
divide :: a -> b -> c
instance Divide Int Int Int where
divide = div
instance Divide Int Double Double where
divide = (/) . fromIntegral
instance Divide Double Int Double where
divide = (. fromIntegral) . (/)
instance Divide Double Double Double where
divide = (/)
我还应该补充的另一件事是 FunctionalDependencies
和 MultiParamTypeClasses
似乎也更加简洁(无论如何对于上面的示例),因为您只需要编写类型一次,并且您不必想出一个虚拟类型名称,然后您必须像使用 TypeFamilies
:
一样为每个实例键入该名称
instance FooBar LongTypeName LongerTypeName where
FooBarResult LongTypeName LongerTypeName = LongestTypeName
fooBar = someFunction
对比:
instance FooBar LongTypeName LongerTypeName LongestTypeName where
fooBar = someFunction
所以除非我确信否则我真的不应该理会 TypeFamilies
而只使用 FunctionalDependencies
和 MultiParamTypeClasses
。因为据我所知,它将使我的代码更简洁、更一致(需要关心的扩展更少),并且还会给我更多的灵活性,例如开放类型关系或双射关系(后者可能是 GHC 的求解器) 8).
这里有一个例子,说明 TypeFamilies
与 MultiParamClasses
和 FunctionalDependencies
相比真正闪耀的地方。事实上,我挑战你想出一个等效的 MultiParamClasses
解决方案,即使是使用 FlexibleInstances
、OverlappingInstance
等
的解决方案
考虑类型级别替换的问题(我 运行 在 Quipper 中 QData.hs
中跨越了这个的特定变体。本质上,您要做的是递归地将一种类型替换为另一种类型。例如,我希望能够
- 用
Int
代替Either [Int] String
中的Bool
得到Either [Bool] String
,
- 用
[Int]
代替Either [Int] String
中的Bool
得到Either Bool String
,
- 用
[Int]
代替Either [Int] String
中的[Bool]
得到Either [Bool] String
.
总而言之,我想要类型级替换的通常概念。对于封闭类型族,我可以为任何类型执行此操作(尽管我需要为每个更高种类的类型构造函数添加一行 - 我在 * -> * -> * -> * -> *
处停止)。
{-# LANGUAGE TypeFamilies #-}
-- Subsitute type `x` for type `y` in type `a`
type family Substitute x y a where
Substitute x y x = y
Substitute x y (k a b c d) = k (Substitute x y a) (Substitute x y b) (Substitute x y c) (Substitute x y d)
Substitute x y (k a b c) = k (Substitute x y a) (Substitute x y b) (Substitute x y c)
Substitute x y (k a b) = k (Substitute x y a) (Substitute x y b)
Substitute x y (k a) = k (Substitute x y a)
Substitute x y a = a
然后尝试 ghci
我得到了想要的输出:
> :t undefined :: Substitute Int Bool (Either [Int] String)
undefined :: Either [Bool] [Char]
> :t undefined :: Substitute [Int] Bool (Either [Int] String)
undefined :: Either Bool [Char]
> :t undefined :: Substitute [Int] [Bool] (Either [Int] String)
undefined :: Either [Bool] [Char]
话虽如此,也许您应该问自己 为什么我使用 MultiParamClasses
而不是 TypeFamilies
。在您上面给出的示例中,除了 Convert
t运行 之外的所有示例都被指定为类型族(尽管每个实例都需要额外的一行用于 type
声明)。
然后,对于 Convert
,我不认为定义这样的东西是个好主意。 Convert
的自然扩展是
这样的实例
instance (Convert a b, Convert b c) => Convert a c where
convert = convert . convert
instance Convert a a where
convert = id
它们对于 GHC 来说是不可解析的,因为它们写起来很优雅...
需要说明的是,我并不是说 MultiParamClasses
没有用处,只是说在可能的情况下您应该使用 TypeFamilies
- 它们让您考虑类型级函数,而不仅仅是关系。
This old HaskellWiki page does an OK job of comparing the two.
编辑
我从 8 月偶然发现了更多对比和历史 blog
Type families grew out of the need to have type classes with
associated types. The latter is not strictly necessary since it can be
emulated with multi-parameter type classes, but it gives a much nicer
notation in many cases. The same is true for type families; they can
also be emulated by multi-parameter type classes. But MPTC gives a
very logic programming style of doing type computation; whereas type
families (which are just type functions that can pattern match on the
arguments) is like functional programming.
Using closed type families
adds some extra strength that cannot be achieved by type classes. To
get the same power from type classes we would need to add closed type
classes. Which would be quite useful; this is what instance chains
gives you.
函数依赖只影响约束求解过程,而类型族引入了非句法类型相等的概念,在 GHC 的中间形式中用强制转换表示。这意味着类型族可以更好地与 GADT 交互。请参阅 了解函数依赖性如何在此处失败的典型示例。
我玩过 TypeFamilies
、FunctionalDependencies
和 MultiParamTypeClasses
。在我看来,TypeFamilies
似乎没有在其他两个上添加任何具体功能。 (但反之则不然)。但我知道字体家族很受欢迎,所以我觉得我错过了一些东西:
"open" 类型之间的关系,例如转换函数,TypeFamilies
似乎不可能。完成 MultiParamTypeClasses
:
class Convert a b where
convert :: a -> b
instance Convert Foo Bar where
convert = foo2Bar
instance Convert Foo Baz where
convert = foo2Baz
instance Convert Bar Baz where
convert = bar2Baz
类型之间的满射关系,例如一种类型安全的伪鸭子类型机制,通常使用标准类型族来完成。完成 MultiParamTypeClasses
和 FunctionalDependencies
:
class HasLength a b | a -> b where
getLength :: a -> b
instance HasLength [a] Int where
getLength = length
instance HasLength (Set a) Int where
getLength = S.size
instance HasLength Event DateDiff where
getLength = dateDiff (start event) (end event)
类型之间的双射关系,例如对于未装箱的容器,这可以通过 TypeFamilies
与数据族来完成,尽管您必须为每个包含的类型声明一个新的数据类型,例如一个newtype
。要么是那个,要么是单射类型族,我认为在 GHC 8 之前不可用。使用 MultiParamTypeClasses
和 FunctionalDependencies
:
class Unboxed a b | a -> b, b -> a where
toList :: a -> [b]
fromList :: [b] -> a
instance Unboxed FooVector Foo where
toList = fooVector2List
fromList = list2FooVector
instance Unboxed BarVector Bar where
toList = barVector2List
fromList = list2BarVector
最后是两种类型和第三种类型之间的满射关系,例如python2或java式除法函数,可以用TypeFamilies
也可以用MultiParamTypeClasses
。完成 MultiParamTypeClasses
和 FunctionalDependencies
:
class Divide a b c | a b -> c where
divide :: a -> b -> c
instance Divide Int Int Int where
divide = div
instance Divide Int Double Double where
divide = (/) . fromIntegral
instance Divide Double Int Double where
divide = (. fromIntegral) . (/)
instance Divide Double Double Double where
divide = (/)
我还应该补充的另一件事是 FunctionalDependencies
和 MultiParamTypeClasses
似乎也更加简洁(无论如何对于上面的示例),因为您只需要编写类型一次,并且您不必想出一个虚拟类型名称,然后您必须像使用 TypeFamilies
:
instance FooBar LongTypeName LongerTypeName where
FooBarResult LongTypeName LongerTypeName = LongestTypeName
fooBar = someFunction
对比:
instance FooBar LongTypeName LongerTypeName LongestTypeName where
fooBar = someFunction
所以除非我确信否则我真的不应该理会 TypeFamilies
而只使用 FunctionalDependencies
和 MultiParamTypeClasses
。因为据我所知,它将使我的代码更简洁、更一致(需要关心的扩展更少),并且还会给我更多的灵活性,例如开放类型关系或双射关系(后者可能是 GHC 的求解器) 8).
这里有一个例子,说明 TypeFamilies
与 MultiParamClasses
和 FunctionalDependencies
相比真正闪耀的地方。事实上,我挑战你想出一个等效的 MultiParamClasses
解决方案,即使是使用 FlexibleInstances
、OverlappingInstance
等
考虑类型级别替换的问题(我 运行 在 Quipper 中 QData.hs
中跨越了这个的特定变体。本质上,您要做的是递归地将一种类型替换为另一种类型。例如,我希望能够
- 用
Int
代替Either [Int] String
中的Bool
得到Either [Bool] String
, - 用
[Int]
代替Either [Int] String
中的Bool
得到Either Bool String
, - 用
[Int]
代替Either [Int] String
中的[Bool]
得到Either [Bool] String
.
总而言之,我想要类型级替换的通常概念。对于封闭类型族,我可以为任何类型执行此操作(尽管我需要为每个更高种类的类型构造函数添加一行 - 我在 * -> * -> * -> * -> *
处停止)。
{-# LANGUAGE TypeFamilies #-}
-- Subsitute type `x` for type `y` in type `a`
type family Substitute x y a where
Substitute x y x = y
Substitute x y (k a b c d) = k (Substitute x y a) (Substitute x y b) (Substitute x y c) (Substitute x y d)
Substitute x y (k a b c) = k (Substitute x y a) (Substitute x y b) (Substitute x y c)
Substitute x y (k a b) = k (Substitute x y a) (Substitute x y b)
Substitute x y (k a) = k (Substitute x y a)
Substitute x y a = a
然后尝试 ghci
我得到了想要的输出:
> :t undefined :: Substitute Int Bool (Either [Int] String)
undefined :: Either [Bool] [Char]
> :t undefined :: Substitute [Int] Bool (Either [Int] String)
undefined :: Either Bool [Char]
> :t undefined :: Substitute [Int] [Bool] (Either [Int] String)
undefined :: Either [Bool] [Char]
话虽如此,也许您应该问自己 为什么我使用 MultiParamClasses
而不是 TypeFamilies
。在您上面给出的示例中,除了 Convert
t运行 之外的所有示例都被指定为类型族(尽管每个实例都需要额外的一行用于 type
声明)。
然后,对于 Convert
,我不认为定义这样的东西是个好主意。 Convert
的自然扩展是
instance (Convert a b, Convert b c) => Convert a c where
convert = convert . convert
instance Convert a a where
convert = id
它们对于 GHC 来说是不可解析的,因为它们写起来很优雅...
需要说明的是,我并不是说 MultiParamClasses
没有用处,只是说在可能的情况下您应该使用 TypeFamilies
- 它们让您考虑类型级函数,而不仅仅是关系。
This old HaskellWiki page does an OK job of comparing the two.
编辑
我从 8 月偶然发现了更多对比和历史 blog
Type families grew out of the need to have type classes with associated types. The latter is not strictly necessary since it can be emulated with multi-parameter type classes, but it gives a much nicer notation in many cases. The same is true for type families; they can also be emulated by multi-parameter type classes. But MPTC gives a very logic programming style of doing type computation; whereas type families (which are just type functions that can pattern match on the arguments) is like functional programming.
Using closed type families adds some extra strength that cannot be achieved by type classes. To get the same power from type classes we would need to add closed type classes. Which would be quite useful; this is what instance chains gives you.
函数依赖只影响约束求解过程,而类型族引入了非句法类型相等的概念,在 GHC 的中间形式中用强制转换表示。这意味着类型族可以更好地与 GADT 交互。请参阅