我正在 Brent Yorgey 的 2013 年 UPenn lecture 中进行练习以实现 mapA
。mapA :: Applicative f => (a -> f b) -> ([a] -> f [b])
我试图获得对这个函数的直觉。这个功能有什么用?我不是在质疑它的实用性——只是想了解它。
此外,我正在寻找从 a -> f b
到 [a] -> f [b]
的提示。
最佳答案
如果我们对 f
一无所知,那么像 a -> f b
这样的函数就可以让我们将东西放入 f
-boxes 中,但那样我们就会完全陷入困境。您可能熟悉 Functor
。如果我们知道 f
是一个 Functor
,那么我们就可以转换 f
里面的东西,但我们仍然基本上被卡住了—— f
形成了一个我们无法跨越的不动的墙。
我们为什么要关心?好吧,当我们尝试构造函数 [a] -> f [b]
时,我们需要了解如何对 a
的集合进行操作。如果我们喜欢(并且它存在),我们也许可以将第一个拉下来,然后通过 a -> f b
将其输入,然后将结果包装在一个列表中:
unsatisfying :: Functor f => (a -> f b) -> ([a] -> f [b])
unsatisfying inject (a : _) = fmap (\x -> [x]) (inject a)
但我们不仅在
[a]
上有一个不完整的模式匹配,我们显然违反了这个函数的精神——我们更喜欢使用所有的 a
s。不幸的是,只知道 f
或什至知道 f
是 Functor
只能让我们stillUnsatisfying :: Functor f => (a -> f b) -> ([a] -> [f b])
stillUnsatisfying inject as = map inject as
问题是,仅仅因为我们有一组
f
-containers 并不意味着我们可以找到任何方法来共同处理它们。我们想以某种方式将我们的集合 [f b]
全部“粘合”在一起。如果我们可以这样做,那么像 [a] -> f [b]
这样的函数听起来就像“将我们的列表 [a]
分解成多个部分,使用 f
将它们分别传递到 inject
中,将所有 (f b)
组合在一起,然后在内部重新组装列表”。很明显,我们需要一种将
Functor
s“glom”在一起的方法,以及一种对 f
的“内部”单独部分进行操作的方法。所以这就是
Applicative
的用武之地。不过,我不打算确切地介绍它。相反,让我们看一个等效的类型类class Functor f => Monoidal f where
basic :: a -> f a
glom :: f a -> f b -> f (a, b)
证明
Monoidal
和 Applicative
是等价的是一个有趣的练习,但是您可以立即看到 glom
提供了我们正在寻找的内容。此外,basic
/pure
使我们能够在需要时将列表的原始部分注入(inject) f
(例如,如果我们的 [a]
为空,那么我们需要将空列表注入(inject) f
而不使用 a -> f b
,因为我们不能- -- 看起来像 basic [] :: f [b]
)。因此
Applicative
不仅可以在仿函数内部进行转换,还可以将一堆仿函数组合在一起,并在仿函数内部对它们的所有部分进行操作。关于haskell - 理解 `mapA` 以获得直觉,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27728255/