加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-14 04:55:55 所属栏目:百科 来源:网络整理
导读:我玩过TypeFamilies,FunctionalDependencies和MultiParamTypeClasses.在我看来,好像TypeFamilies没有添加任何具体的功能而不是其他两个. (但反之亦然).但我知道类型家庭非常受欢迎所以我觉得我错过了一些东西: 类型之间的“开放”关系,例如转换函数,使用Typ
我玩过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.无论是那个还是一个内射类型的家族,我认为在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样式除法函数,它们也可以通过使用MultiParamTypeClasses与TypeFamilies完成.使用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).

解决方法

下面是一个示例,与具有FunctionalDependencies的MultiParamClasses相比,TypeFamilies真正发挥作用.事实上,我挑战你提出一个等效的MultiParamClasses解决方案,即使是使用FlexibleInstances,OverlappingInstance等的解决方案.

考虑类型级别替换的问题(我在QData.hs中遇到过Quipper的特定变体).基本上你想要做的是以递归方式将一种类型替换为另一种类型.例如,我希望能够

>在[Int]字符串中用Int替换Bool并获取[Bool]字符串,
>在[Int]字符串中将[Int]替换为Bool并获取任一个Bool字符串,
>在[Int]字符串中将[Int]替换为[Bool]并获取[Bool]字符串.

总而言之,我想要通常的类型级别替换概念.对于一个封闭类型的家庭,我可以为任何类型执行此操作(虽然我需要为每个更高级的类型构造函数添加额外的行 – 我停在* – > * – > * – > * – > *).

{-# 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,我不相信定义这样的东西是个好主意. 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.

编辑

我从08002年8月偶然发现了一些对比和历史

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.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读