我玩过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
与数据系列完成,但是你必须为每个包含的类型声明一个新的数据类型,例如使用{ {1}}。无论是那个还是一个内射型家庭,我认为在GHC 8之前是不可用的。完成了newtype
和MultiParamTypeClasses
:
FunctionalDependencies
最后是两种类型和第三种类型之间的主观关系,例如python2或java样式除法函数,可以使用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
通过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
VS
instance FooBar LongTypeName LongerTypeName where
FooBarResult LongTypeName LongerTypeName = LongestTypeName
fooBar = someFunction
所以,除非我确信,否则我真的好像不应该为instance FooBar LongTypeName LongerTypeName LongestTypeName where
fooBar = someFunction
而烦恼,只使用TypeFamilies
和FunctionalDependencies
。因为据我所知,它会使我的代码更简洁,更一致(更少关注的扩展),并且还会给我更多的灵活性,例如开放式关系或双向关系(后者可能是GHC的解决方案) 8)。
答案 0 :(得分:3)
以下是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
偶然发现了一些对比和历史类型族不再需要使用类型类 相关类型。后者并非严格必要,因为它可以 使用多参数类型类进行模拟,但它提供了更好的 在许多情况下的表示法。类型家庭也是如此;他们能 也可以通过多参数类型类进行模拟。但MPTC给出了一个 非常逻辑编程风格的做式计算;而类型 family(它们只是可以模拟匹配的类型函数) arguments)就像函数式编程一样。
使用封闭式家庭 增加了类型类无法实现的额外强度。至 从类型类中获得相同的功能,我们需要添加闭合类型 类。哪个非常有用;这就是实例链 给你。
答案 1 :(得分:1)
函数依赖只影响约束求解的过程,而类型族引入了非句法类型相等的概念,通过强制表示为GHC的中间形式。这意味着类型家族与GADT的互动更好。有关功能依赖性如何失败的规范示例,请参阅this question。