在Maybe类型上应用函数?

时间:2017-11-26 17:20:57

标签: function haskell maybe

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数据类型的工作方式。 谢谢你的帮助!

3 个答案:

答案 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以获得af个。{例如,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实现。外部的MaybeMaybefmap实例。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