理解`mapA`获得直觉

时间:2015-01-01 04:26:23

标签: haskell

我正在进行Brent Yorgey 2013年UPenn lecture的练习,以实施mapA

mapA :: Applicative f => (a -> f b) -> ([a] -> f [b])

我试图获得这个功能的直觉。这个功能有用吗?我并没有质疑它的实用性 - 只是想了解它。

此外,我正在寻找从a -> f b[a] -> f [b]的提示。

3 个答案:

答案 0 :(得分:3)

如果我们对f一无所知,那么像a -> f b这样的函数会让我们把东西放进f - 盒子中,但那时我们会被完全卡住。您可能熟悉Functor。如果我们知道fFunctor,那么我们就可以转换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秒。不幸的是,只知道f甚至只知道f Functor只能让我们知道

stillUnsatisfying :: Functor f => (a -> f b) -> ([a] -> [f b])
stillUnsatisfying inject as = map inject as

问题在于,仅仅因为我们有一个f的集合 - 容器并不意味着我们可以找到任何集体对待它们的方法。我们想以某种方式“粘合”我们的集合[f b]。如果我们可以这样做,那么像[a] -> f [b]这样的函数听起来就像是“将我们的列表[a]分解成碎片,使用f将它们分别传递到inject,所有这些都是(f b)一起,然后重新组合内部的列表“。

显然,我们需要一种方法来“混淆”Functor,并且还需要一种方法来处理f内部的单独部分。

所以这就是Applicative的用武之地。不过,我不会完全介绍它。相反,让我们看一下等效的类型

class Functor f => Monoidal f where
  basic :: a -> f a
  glom  :: f a -> f b -> f (a, b)

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

所以Applicative让你不仅可以在函子内部进行转换,还可以将一堆函子放在一起,并在函数内部运行它们的所有部分。

答案 1 :(得分:1)

你已经到了中途,但你正在寻找的最后一项功能是在LYAH中定义的sequenceA功能。这是我的实施:

sequenceA :: Applicative f => [f a] -> f [a]
sequenceA = foldr ((<*>) . fmap (:)) (pure [])

从那里mapA功能很容易。只需添加另一个参数并组合两个函数:

mapA :: Applicative f => (a -> f b) -> [a] -> f [b]
mapA f xs = foldr ((<*>) . fmap (:) . f) (pure []) xs
-- Or, you could implement it this more elegant, albeit slightly slower, way:
mapA = (sequenceA .) . map

你有一个实现供你查看。至于实用性,它在IO中特别有用,当你有一个像["Foo","Bar","Baz"]这样的列表时,你想要putStrLn所有这些功能。这样做,您需要映射每个值以及sequence mapAApplicative。请注意,这些函数具有monadic等价物,但以纯Applicative方式实现它们是一个很好的练习。

这些函数在处理fmap列表时非常有用,允许人们在不使用半吨{{1}}的情况下更轻松地操纵其中的值。

答案 2 :(得分:0)

我们已经知道map :: (a -> b) -> ([a] -> [b])很有用。它将函数应用于列表的每个元素。

a -> f bf的宽松但启发性的解释是,它是一个函数,它接受a,执行一个应用“动作”,然后返回{{1} }}。例如,如果bf,那么“操作”可能是从磁盘读取的。 IO可以解释为将此“函数”应用于列表的每个元素。