【问题标题】:Haskell: Typeclass vs passing a functionHaskell:类型类与传递函数
【发布时间】:2020-04-16 05:22:26
【问题描述】:

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

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

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

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

我们不能只定义 elem 函数而不使用类型类,而是传递一个函数参数来完成这项工作吗?

【问题讨论】:

  • 这就是所谓的字典传递。您可以将类型类约束视为隐式参数。
  • 您可以这样做,但显然不必传递函数并根据类型使用“标准”函数会更方便。
  • 你可以这样说,是的。但我认为至少还有一个其他重要优势:编写多态函数的能力,这些函数适用于实现特定“接口”或一组特性的任何类型。我认为类型类约束以传递额外函数参数所没有的方式非常清楚地表达了这一点。特别是因为许多类型类必须满足的(可悲的是隐含的)“法则”。 Monad m 约束对我来说比传递 a -> m am a -> (a -> m b) -> m b 类型的附加函数参数更重要。
  • TypeApplications 扩展让您可以将隐式参数显式化。 (==) @Int 3 5 比较 35 专门作为 Int 值。您可以将@Int 视为特定类型相等函数字典中的键,而不是Int 特定比较函数本身。

标签: haskell functional-programming higher-order-functions


【解决方案1】:

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

当然,只要表现力有所不同,就需要权衡取舍。如果使用 DPS 编写,您可以以更多方式使用给定 API,但如果您不能,该 API 会获取更多信息。这在实践中出现的一种方式是在Data.Set 中,它依赖于每个类型只有一个Ord 字典这一事实。 Set 存储根据Ord 排序的元素,如果你用一个字典构建一个集合,然后使用另一个字典插入一个元素,就像 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.* 组合器中,这与 deriving via 一起使用效果很好,其中您基本上可以获得为您编写的类型的所有“标准”代数结构。

甚至不要让我开始研究 MPTC 和fundeps,它们将信息反馈给类型检查和推理。我从未尝试过将这样的东西转换为 DPS——我怀疑它会涉及传递大量类型相等性证明——但无论如何,我确信这对我的大脑来说比我感到舒服的要多得多与。

--

1U除非你使用reflection,在这种情况下,它们的功能相当——但reflection 使用起来也很麻烦。

【讨论】:

【解决方案2】:

是的。这(称为字典传递)基本上是编译器对类型类所做的。对于那个函数,从字面上看,它看起来有点像这样:

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。 (事实上​​,in the standard library, elem = any . (==)。)

【讨论】:

  • AFAIU 字典传递是 Scala 编码类型类的方法。这些额外的参数可以声明为implicit,编译器会从作用域中为你注入它们。
猜你喜欢
  • 2013-11-10
  • 1970-01-01
  • 2021-12-31
  • 2013-08-14
  • 1970-01-01
  • 2011-08-28
  • 1970-01-01
  • 2011-02-20
  • 1970-01-01
相关资源
最近更新 更多