Haskell的新手,我无法弄清楚如何将一个函数(a - > b)应用到一个列表中[也许一个]并获得[Maybe b]
maybeX:: (a -> b) -> [Maybe a] -> [Maybe b]
该函数应该与map执行完全相同的操作,将函数f应用于Maybe语句列表中,如果它只是它返回一个f Just,如果它只是一个Nothing。 像下面的例子一样,我想在以下列表的每个元素上添加+5:
[Just 1,Just 2,Nothing,Just 3]
并获取
[Just 6,Just 7,Nothing,Just 8]
真的想弄清楚这个并且我尝试了很多,但似乎总是这个我不知道这个Maybe数据类型的工作方式。 谢谢你的帮助!
答案 0 :(得分:11)
让我们首先定义如何对单个Maybe
采取行动,然后我们将其扩展到整个列表。
mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapMaybe f Nothing = Nothing
mapMaybe f (Just x) = Just (f x)
如果Maybe
包含值,mapMaybe
会对其应用f
,如果它不包含值,那么我们只返回一个空Maybe
但我们有Maybe
的列表,因此我们需要对每个 {}进行mapMaybe
。
mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
mapMaybes f ms = [mapMaybe f m | m <- ms]
我在这里使用列表推导来评估mapMaybe f m
中每个m
的{{1}}。
现在有了更先进的技术。将函数应用于容器中的每个值的模式由ms
类型类捕获。
Functor
如果您可以编写一个函数,从class Functor f where
fmap :: (a -> b) -> f a -> f b
到f
,则Functor
类型为a
,并将该函数应用于b
完整f
以获得a
个f
个。{例如,b
和[]
都是Maybe
s:
Functor
instance Functor Maybe where
fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)
instance Functor [] where
fmap f xs = [f x | x <- xs]
的{{1}}版本与我上面写的Maybe
相同,fmap
的实现使用了列表理解将mapMaybe
应用于列表中的每个元素。
现在,要编写[]
,您需要使用f
的{{1}}版本对列表中的每个项目进行操作,然后对单个{{}进行操作1}}使用mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
的{{1}}版本。
[]
请注意,我们实际上在这里调用两个不同的fmap
实现。外部的Maybe
是Maybe
个fmap
实例。mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
mapMaybes f ms = fmap (fmap f) ms
-- or:
mapMaybes = fmap . fmap
fmap
实例。内在的是fmap :: (Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b]
。
还有一个附录 - 尽管这是非常深奥的,所以如果你不了解这里的一切,请不要担心。我只想给你一些我觉得很酷的东西。
这个&#34; []
s&#34;链样式(Functor
)是向下钻取结构的多个层次的常见技巧。每个(a -> b) -> Maybe a -> Maybe b
都有fmap
类型,因此当您使用fmap . fmap . fmap ...
撰写时,您需要构建更高阶的函数。
fmap
因此,如果你有一个仿函数(仿函数......)的仿函数,那么 n (a -> b) -> (f a -> f b)
将允许你在 n 级别映射元素结构。 Conal Elliot称这种风格为"semantic editor combinators"。
这个技巧也适用于(.)
,这是一种有效的fmap :: Functor g => (f a -> f b) -> (g (f a) -> g (f b))
fmap :: Functor f => (a -> b) -> (f a -> f b)
-- so...
fmap . fmap :: (Functor f, Functor g) => (a -> b) -> g (f a) -> g (f b)
&#34;。
fmap
(我省略了traverse :: (Traversable t, Applicative f) => (a -> f b) -> (t a -> f (t b))
之前的位,因为我用完了水平空间。)因此,如果你有一个可遍历的遍历(可遍历......),你可以对元素执行有效的计算只需编写fmap
n 次,即可 n 。编写这样的遍历是traverse :: (...) => (t a -> f (t b)) -> (s (t a) -> f (s (t b)))
traverse :: (...) => (a -> f b) -> (t a -> f (t b))
-- so...
traverse . traverse :: (...) => (a -> f b) -> s (t a) -> f (s (t b))
库背后的基本思想。
答案 1 :(得分:4)
[注意:这假设熟悉仿函数。]
另一种方法是使用>>> getCompose (fmap (+5) (Compose [Just 1, Just 2, Nothing, Just 3]))
[Just 6,Just 7,Nothing,Just 8]
中定义的类型级别合成。
maybeX
您可以将其抽象为-- Wrap, map, and unwrap
maybeX :: (a -> b) -> [Maybe a] -> [Maybe b]
maybeX f = getCompose . fmap f . Compose
的定义:
Maybe
(事实上,定义中没有任何内容假定为[]
或FlexibleContexts
,只有受限制的类型注释。如果启用(Functor g, Functor f) => (a1 -> a) -> f (g a1) -> f (g a)
扩展名,则可以推断类型{{1}并在任意嵌套的仿函数上使用它。
>>> maybeX (+1) (Just [1,2])
Just [2,3]
>>> maybeX (+1) [[1,2]]
[[2,3]]
>>> maybeX (+1) Nothing -- type (Num a, Functor g) => Maybe (g a), since `Nothing :: Maybe a`
)
Compose
构造函数将[]
和Maybe
类型构造函数组合成一个新类型构造函数:
>>> :k Compose
Compose :: (k1 -> *) -> (k -> k1) -> k -> *
比较
Compose :: (k1 -> *) -> (k -> k1) -> k -> *
(.) :: (b -> c) -> (a -> b) -> a -> c
(主要区别在于(.)
可以组成任何两个函数;看起来Compose
及其关联实例需要将最后一个较高的值应用于具体类型,一种类型*
。)
将数据构造函数Compose
应用于Maybes列表会生成包装值
>>> :t Compose [Nothing]
Compose [Nothing] :: Compose [] Maybe a
只要Compose
的参数本身是Functor
的实例([]
和Maybe
为),Compose f g
也< / em>一个仿函数,因此您可以使用fmap
:
>>> fmap (+5) (Compose [Just 1,Nothing,Just 2,Just 3])
Compose [Just 6,Nothing,Just 7,Just 8]
Compose
值只是原始值的包装:
>>> getCompose $ fmap (+5) (Compose [Just 1,Nothing,Just 2,Just 3])
[Just 6,Nothing,Just 7,Just 8]
最后,这与简单直接撰写fmap
并不相同:
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose x) = Compose (fmap (fmap f) x)
不同之处在于您可以使用函数定义类型并免费获取其伴随的Functor
实例,而不必手动编码。
答案 2 :(得分:2)
您可能已经知道
map :: (a -> b) -> [a] -> [b]
......这实际上只是
的一个特例fmap :: Functor f => (a -> b) -> f a -> f b
后者适用于列表(其行为与map
完全相同)和Maybe
上,因为两者都是仿函数。即,以下两个签名都是有效的特化:
fmap :: (a -> b) -> [a] -> [b]
fmap :: (a -> b) -> Maybe a -> Maybe b
现在,您的用例看起来很相似,但遗憾的是[Maybe a]
本身并不是f a
的特化,而是f (g a)
形式。但请注意,我们可以将变量α
替换为g a
,即Maybe a
,然后我们可以使用
fmap :: (α -> β) -> [α] -> [β]
即
fmap :: (Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b]
看起来更像是你想要的签名!但是,我们仍然需要一个带有签名Maybe a -> Maybe b
的函数。好吧,看看上面......我再说一遍:
fmap :: (a -> b) -> Maybe a -> Maybe b
这可以部分应用,即当你有一个函数φ :: a -> b
时,你可以轻松获得函数fmap φ :: Maybe a -> Maybe b
。这解决了你的问题:
maybeX :: (a -> b) -> [Maybe a] -> [Maybe b]
maybeX φ = fmap (fmap φ)
......或者,如果你想得到额外的幻想,
maybeX = fmap . fmap