Haskell:Typeclass与传递函数


16

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

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

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

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

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


2
这就是所谓的字典传递。您可以将类型类约束视为隐式参数。
Poscat

2
您可以这样做,但是显然不必传递函数,而只是根据类型使用“标准”函数会更方便。
罗宾·齐格蒙德

2
您可以这样说,是的。但是我认为至少还有另一个重要的优点:编写可在实现特定“接口”或功能集的任何类型上工作的多态函数的能力。我认为类型类约束非常清楚地表达了这一点,而传递额外的函数参数则没有。特别是由于许多类型类必须满足的(非常隐含的)“定律”。一个Monad m约束不是通过类型的附加功能参数说更多的我a -> m am a -> (a -> m b) -> m b
罗宾·齐格蒙德


1
TypeApplications扩展使您可以使隐式参数明确。 (==) @Int 3 5比较35特别是作为Int值。您可以将其@Int视为类型特定的相等函数字典中的键,而不是Int-特定比较函数本身。
chepner

Answers:


19

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

当然,每当表达能力存在差异时,就要进行权衡。如果使用DPS编写给定的API,则可以通过更多方式使用该API,但如果不这样做,则该API将获得更多信息。Data.Set实际上,这是一种显示方式,它依赖于Ord每个类型只有一个字典这一事实。该Set存储其元素按照分类Ord,如果你建立了一套与一个字典,然后插入使用不同的一个元素,将有可能DPS,你可能会打破和Set的不变,并导致崩溃。可以使用幻像存在缓解此唯一性问题类型来标记字典,但同样,代价是API的烦人的复杂性。这在TypeableAPI中也以几乎相同的方式显示。

唯一性位并不经常出现。类型类最擅长为您编写代码。例如,

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.*组合器中一样,这deriving via在您可以从中获取所有为您编写的“标准”类型的代数结构。

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

-

1 ü nless您使用reflection在他们成为这种情况下,在功率相当的-但reflection也可以使用麻烦。



我对通过DPS表达的二头肌非常感兴趣。您是否知道有关此主题的一些可取资源?无论如何,非常容易理解的解释。
bob

@bob,不是临时的,但是这将是一个有趣的探索。也许问一个新的问题?
luqui

5

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

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现在等同于elem x xs

...等一下。就是这样any。(实际上,在标准库中elem = any . (==)。)


AFAIU字典传递是Scala编码类型类的方法。这些额外的参数可以声明为implicit,编译器将从范围中为您注入它们。
michid
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.