Haskell:Typeclass与传递函数

问题描述 投票:10回答:2

对我来说,您似乎总是可以传递函数参数,而不是使用类型类。例如,而不是定义相等类型类:

class Eq a where 
  (==)                  :: a -> a -> Bool

并且在其他函数中使用它来指示类型实参必须是Eq的实例:

elem                    :: (Eq a) => a -> [a] -> Bool

我们是否不能不使用类型类而仅定义elem函数,而是传递可以完成工作的函数参数?

haskell functional-programming higher-order-functions
2个回答
14
投票

是。这被称为“字典通过风格”。有时,当我做一些特别棘手的事情时,我需要废弃一个类型类并将其转换为字典,因为字典传递功能更强大1,但通常非常麻烦,从而使概念上简单的代码看起来相当复杂。有时,我在不是Haskell的语言中使用字典传递样式来模拟类型类(但已经了解到,通常这并不像听起来那样好)。

当然,只要表达能力有差异,就需要进行权衡。如果使用DPS编写给定的API,则可以通过更多方式使用它,但如果不这样做,则该API将获得更多信息。实践中显示的一种方式是在Data.Set中,它依赖于每个类型只有一个Ord词典的事实。 Set存储根据Ord排序的元素,如果您用一个字典构建一个集合,然后使用DPS插入一个不同的元素(如DPS),则可能会破坏Set的不变式并使其崩溃。可以使用phantom existential类型标记字典来缓解这种唯一性问题,但是同样,这会以API中相当烦人的复杂性为代价。这在Typeable API中也以几乎相同的方式显示。

唯一性位很少出现。类型类最擅长为您编写代码。例如,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

[需要两个“处理器”,它们接受一个输入并可能提供一个输出,并将它们连接起来,展平Nothing,必须在DPS中编写如下所示:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

即使我们已经在类型签名中将其拼写出来了,并且编译器已经知道了,我们基本上不得不再次拼写我们正在使用它的类型。因为只有一种方法可以在类型上构造给定的Semigroup,所以编译器会为您完成此操作。当您开始定义许多参数实例并使用类型的结构来为您计算时,这会产生“复合兴趣”类型的效果,如Data.Functor.*组合器中那样,并且在Data.Functor.*的情况下,这种效果非常有用您基本上可以为您编写所有类型的“标准”代数结构。

而且甚至不让我开始学习MPTC和Fundeps,它们将信息反馈给类型检查和推断。我从来没有尝试过将这种东西转换为DPS-我怀疑这会涉及传递很多类型相等性证明-但无论如何,我敢肯定,这对我的大脑来说会比我舒适的工作多得多与。

-

1 U 除非使用deriving via,否则它们的功率相等-但deriving via的使用也很麻烦。


4
投票

是。基本上,编译器所做的(称为字典传递)就是要进行类型分类的。对于该功能,从字面上看,它看起来像这样:

reflection

呼叫reflection现在等效于reflection。在这种特定情况下,您可以走得更远:elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool elemBy _ _ [] = False elemBy eq x (y:ys) = eq x y || elemBy eq x ys 每次都有相同的第一个参数,因此您可以使调用者有责任使用该参数,并最终得到:

elemBy (==) x xs

呼叫elem x xs现在等效于eq

...哦,等等。就是elemBy2 :: (a -> Bool) -> [a] -> Bool elemBy2 _ [] = False elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys 。 (实际上是elemBy2 (x ==) xs。)

© www.soinside.com 2019 - 2024. All rights reserved.