GHC有几种有用的语言extensions用于机械地导出各种常见的Haskell类型类( -XDeriveFunctor
, -XDeriveFoldable
, -XDeriveTraversable
) . 似乎 Applicative
是另一个经常需要且经常容易派生的类 . 对于包含 a
类型的插槽的简单记录,例如,
data SimpleRecord a = Simple a a a
Applicative
实例非常简单,
instance Applicative SimpleRecord where
pure x = Simple x x x
Simple a1 b1 c1 <*> Simple a2 b2 c2 = Simple (a1 a2) (b1 b2) (c1 c2)
即使在稍微难以处理的情况下,其他一些应用函数也会隐藏一些 a
值,例如:
data MyRecord f a = MyRecord (f a) a
一个合理的实例很容易写,
instance (Applicative f) => Applicative (MyRecord f) where
pure x = MyRecord (pure x) x
MyRecord a1 b1 <*> MyRecord a2 b2 = MyRecord (a1 <*> a2) (b1 b1)
为什么不存在实现这些类型的机械实例的 -XDeriveApplicative
扩展?甚至 derive
和 generic-derive
包显然缺乏 Applicative
支持 . 是否有理论上的问题阻止这些实例通常有效(超出那些可能也会威胁 Functor
, Foldable
或 Traversable
扩展的原因)?
2 回答
对于遵循仿函数定律的给定数据类型,最多只有一个
Functor
实例 . 例如,对于列表,map
是fmap
的唯一合法实现:但是,可能存在不止一个守法的
Applicative
实例,这不一定是显而易见的 .对于列表,
<*>
的行为类似于\fs xs -> concatMap (\f -> map f xs) fs
或类似zipWith ($)
,并且不清楚编译器应该选择哪一个 .为了回应别人,有's no good reason I know of why we can',我们根本就没有 . 通常有多个
Foldable
和Traversable
的合法实例,我们有一个标志来推导它们 . 有一段时间我们没有关于可穿越法律的真实好故事,但现在we have some . 同样,我们仍然没有Foldable
法律(但我认为我们可以,见here) .在不同的'obvious'应用程序中,例如向前和向后的应用程序(相对于
<*>
本身,甚至在f a
中如果有这样的情况下进行置换),然后按照句法顺序遍历,如此处所 Build 的应用程序,看似合法 . 但是,对于诸如列表之类的递归类型,甚至是具有多个构造函数的类型,我们选择有效的应用程序都会以有趣的方式开花 . 对于类似列表的常规递归类型,明显的应用选择自然是"zipLike"一次,因为它以自然的方式推广非递归情况,将结构与结构匹配 . 对于具有多个构造函数的sum类型,"obvious"选项更难定义 .在我看来,完全合理的applicative派生将按照这里的建议工作,在语法顺序上只有一个构造函数的类型(包括递归的) . 在多个构造函数的情况下,失败似乎是合法的 .
另一方面,虽然可折叠和可穿越似乎经常出现在他们的“明显”形式中,但对于我来说,与有趣的相比,我们希望定义“明显”的应用程序多少次 . 我的直觉告诉我,这个功能很少被运用,也许根本不常用 .