类型族可以做什么,多参数类型类和函数依赖不能

sem*_*lon 5 haskell typeclass type-families functional-dependencies

我已经打得四处TypeFamilies,FunctionalDependenciesMultiParamTypeClasses.在我看来好像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
Run Code Online (Sandbox Code Playgroud)

类型之间的表观关系,例如一种类型安全的伪鸭类型机制,通常用标准类型族来完成.完成MultiParamTypeClassesFunctionalDependencies:

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)
Run Code Online (Sandbox Code Playgroud)

类型之间的双射关系,例如对于未装箱的容器,可以通过TypeFamilies数据族完成,尽管那时你必须为每个包含的类型声明一个新的数据类型,例如a newtype.无论是那个还是一个内射型家庭,我认为在GHC 8之前是不可用的.完成MultiParamTypeClassesFunctionalDependencies:

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
Run Code Online (Sandbox Code Playgroud)

最后是两种类型和第三种类型之间的主观关系,例如python2或java风格的除法函数,它们也可以TypeFamilies通过使用来完成MultiParamTypeClasses.完成MultiParamTypeClassesFunctionalDependencies:

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 = (/)
Run Code Online (Sandbox Code Playgroud)

还有一件事我还要补充的是,它好像FunctionalDependenciesMultiParamTypeClasses也相当更简洁一点(以上反正例子),你只需要编写一次的类型,你不必拿出一个假型然后,您必须为每个实例键入的名称,如下所示TypeFamilies:

instance FooBar LongTypeName LongerTypeName where
    FooBarResult LongTypeName LongerTypeName = LongestTypeName
    fooBar = someFunction
Run Code Online (Sandbox Code Playgroud)

VS:

instance FooBar LongTypeName LongerTypeName LongestTypeName where
    fooBar = someFunction
Run Code Online (Sandbox Code Playgroud)

所以,除非我确信否则它似乎真的喜欢我只是没有费心TypeFamilies且仅使用FunctionalDependenciesMultiParamTypeClasses.因为据我所知,它将使我的代码更简洁,更一致(更少关注的扩展),并且还会给我更多的灵活性,例如开放式关系或双向关系(可能后者是GHC的解决方案) 8).

Ale*_*lec 3

这是一个与withTypeFamilies相比真正出色的示例。事实上,我挑战你想出一个等效的解决方案,甚至是使用、等的解决方案。MultiParamClassesFunctionalDependenciesMultiParamClassesFlexibleInstancesOverlappingInstance

考虑类型级别替换的问题(我在参考资料中的QuiperQData.hs中遇到了一个特定的变体)。本质上,您想要做的就是递归地用一种类型替换另一种类型。例如,我希望能够

  • 代替Intin和Boolget ,Either [Int] StringEither [Bool] String
  • 代替[Int]in和Boolget ,Either [Int] StringEither Bool String
  • 替换[Int]in[Bool]Either [Int] Stringget 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
Run Code Online (Sandbox Code Playgroud)

并尝试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]
Run Code Online (Sandbox Code Playgroud)

话虽如此,也许您应该问自己为什么我使用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
Run Code Online (Sandbox Code Playgroud)

这些对于 GHC 来说是无法解决的,但写起来却很优雅......

需要明确的是,我并不是说 没有任何用途MultiParamClasses,只是在可能的情况下您应该使用TypeFamilies- 它们让您考虑类型级函数而不仅仅是关系。

这个旧的 HaskellWiki 页面很好地比较了两者

编辑

我从 augustss博客中偶然发现了一些更多的对比和历史

类型族是出于对具有相关类型的类型类的需要而产生的。后者并不是绝对必要的,因为它可以用多参数类型类来模拟,但在许多情况下它提供了更好的表示法。对于类型族也是如此;它们也可以通过多参数类型类来模拟。但是MPTC提供了一种非常逻辑的编程风格来进行类型计算;而类型族(只是可以对参数进行模式匹配的类型函数)就像函数式编程。

使用封闭类型族可以增加一些类型类无法实现的额外强度。为了从类型类获得相同的能力,我们需要添加封闭类型类。这将非常有用;这就是实例链为您提供的。