我正在 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 或什至知道 fFunctor 只能让我们
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)

证明 MonoidalApplicative 是等价的是一个有趣的练习,但是您可以立即看到 glom 提供了我们正在寻找的内容。此外,basic/pure 使我们能够在需要时将列表的原始部分注入(inject) f(例如,如果我们的 [a] 为空,那么我们需要将空列表注入(inject) f 而不使用 a -> f b,因为我们不能- -- 看起来像 basic [] :: f [b] )。

因此 Applicative 不仅可以在仿函数内部进行转换,还可以将一堆仿函数组合在一起,并在仿函数内部对它们的所有部分进行操作。

关于haskell - 理解 `mapA` 以获得直觉,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27728255/

10-13 03:07