我玩过TypeFamilies
,FunctionalDependencies
和MultiParamTypeClasses
。在我看来,TypeFamilies
并未在其他两个之上添加任何具体功能。 (但反之亦然)。但是我知道类型家庭非常喜欢,所以我觉得我缺少一些东西:
类型之间的“开放”关系,例如转换函数,对于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
。要么使用这种类型,要么使用注入(inject)型家族,我认为在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
vs:
instance FooBar LongTypeName LongerTypeName LongestTypeName where
fooBar = someFunction
因此,除非我坚信,否则似乎真的不应该只用
TypeFamilies
而是只使用FunctionalDependencies
和MultiParamTypeClasses
。因为据我所知,它将使我的代码更简洁,更一致(无需过多扩展),并且还为我提供了更大的灵活性,例如开放类型关系或双射关系(后者可能是GHC的求解器) 8)。 最佳答案
这是TypeFamilies
与MultiParamClasses
的FunctionalDependencies
相比真正发光的一个示例。实际上,我挑战您提出一种等效的MultiParamClasses
解决方案,甚至是使用FlexibleInstances
,OverlappingInstance
等的解决方案。
考虑类型级别替换的问题(我在QData.hs
的Quipper中遇到了这种情况的特定变体)。本质上,您想要做的是将一种类型递归替换为另一种类型。例如,我希望能够
Int
替换Bool
中的Either [Int] String
并获得Either [Bool] String
,[Int]
替换Bool
中的Either [Int] String
并获得Either Bool String
,[Int]
替换[Bool]
中的Either [Int] String
并获得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
之外的所有示例都转换为类型族(尽管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。
编辑
我从八月blog偶然发现了一些对比和历史
关于haskell - 类型族可以做什么,而多参数类型类和功能依赖项不能做到,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38640052/