我正在进行Brent Yorgey 2013年UPenn lecture的练习,以实施mapA
。
mapA :: Applicative f => (a -> f b) -> ([a] -> f [b])
我试图获得这个功能的直觉。这个功能有用吗?我并没有质疑它的实用性 - 只是想了解它。
此外,我正在寻找从a -> f b
到[a] -> f [b]
的提示。
答案 0 :(得分:3)
如果我们对f
一无所知,那么像a -> f b
这样的函数会让我们把东西放进f
- 盒子中,但那时我们会被完全卡住。您可能熟悉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
秒。不幸的是,只知道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)
这是一个有趣的练习,可以证明Monoidal
和Applicative
是等价的,但是您可以立即看到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
mapA
,Applicative
。请注意,这些函数具有monadic等价物,但以纯Applicative
方式实现它们是一个很好的练习。
这些函数在处理fmap
列表时非常有用,允许人们在不使用半吨{{1}}的情况下更轻松地操纵其中的值。
答案 2 :(得分:0)
我们已经知道map :: (a -> b) -> ([a] -> [b])
很有用。它将函数应用于列表的每个元素。
a -> f b
对f
的宽松但启发性的解释是,它是一个函数,它接受a
,执行一个应用“动作”,然后返回{{1} }}。例如,如果b
为f
,那么“操作”可能是从磁盘读取的。 IO
可以解释为将此“函数”应用于列表的每个元素。