问题
New to Haskell and I can't figure out how apply a function (a -> b) into a list [Maybe a] and get [Maybe b]
maybeX:: (a -> b) -> [Maybe a] -> [Maybe b]
The function is supposed to do the exact same thing as map, apply the function f on a list of Maybe statements and if it Just it returns me a f Just and if it's a Nothing just a Nothing. Like the following example I want to add +5 on every Element of the following List :
[Just 1,Just 2,Nothing,Just 3]
and get
[Just 6,Just 7,Nothing,Just 8]
Really trying to figure this and I tried a lot but it always seems like it is something that I'm not aware of in the way of which this Maybe datatype works.. Thanks for your help!
回答1:
Let's start by defining how to act on a single Maybe
, and then we'll scale it up to a whole list.
mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapMaybe f Nothing = Nothing
mapMaybe f (Just x) = Just (f x)
If the Maybe
contains a value, mapMaybe
applies f
to it, and if it doesn't contain a value then we just return an empty Maybe
.
But we have a list of Maybe
s, so we need to apply mapMaybe
to each of them.
mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
mapMaybes f ms = [mapMaybe f m | m <- ms]
Here I'm using a list comprehension to evaluate mapMaybe f m
for each m
in ms
.
Now for a more advanced technique. The pattern of applying a function to every value in a container is captured by the Functor
type class.
class Functor f where
fmap :: (a -> b) -> f a -> f b
A type f
is a Functor
if you can write a function which takes a function from a
to b
, and applies that function to an f
full of a
s to get an f
full of b
s. For example, []
and Maybe
are both Functor
s:
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]
Maybe
's version of fmap
is the same as the mapMaybe
I wrote above, and []
's implementation uses a list comprehension to apply f
to every element in the list.
Now, to write mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
, you need to operate on each item in the list using []
's version of fmap
, and then operate on the individual Maybe
s using Maybe
's version of fmap
.
mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b]
mapMaybes f ms = fmap (fmap f) ms
-- or:
mapMaybes = fmap . fmap
Note that we're actually calling two different fmap
implementations here. The outer one is fmap :: (Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b]
, which dispatches to []
's Functor
instance. The inner one is (a -> b) -> Maybe a -> Maybe b
.
One more addendum - though this is quite esoteric, so don't worry if you don't understand everything here. I just want to give you a taste of something I think is pretty cool.
This "chain of fmap
s" style (fmap . fmap . fmap ...
) is quite a common trick for drilling down through multiple layers of a structure. Each fmap
has a type of (a -> b) -> (f a -> f b)
, so when you compose them with (.)
you're building a higher-order function.
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)
So if you have a functor of functors (of functors...), then n fmap
s will let you map the elements at level n of the structure. Conal Elliot calls this style "semantic editor combinators".
The trick also works with traverse :: (Traversable t, Applicative f) => (a -> f b) -> (t a -> f (t b))
, which is a kind of "effectful fmap
".
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))
(I omitted the bits before the =>
because I ran out of horizontal space.) So if you have a traversable of traversables (of traversables...), you can perform an effectful computation on the elements at level n just by writing traverse
n times. Composing traversals like this is the basic idea behind the lens
library.
回答2:
[Note: this assumes familiarity with functors.]
Another approach is to use type-level composition as defined in Data.Functor.Compose
.
>>> getCompose (fmap (+5) (Compose [Just 1, Just 2, Nothing, Just 3]))
[Just 6,Just 7,Nothing,Just 8]
You can abstract this into a defintion for your maybeX
:
-- Wrap, map, and unwrap
maybeX :: (a -> b) -> [Maybe a] -> [Maybe b]
maybeX f = getCompose . fmap f . Compose
(In fact, nothing in the definition assumes Maybe
or []
, just the restricted type annotation. If you enable the FlexibleContexts
extension, you can infer the type (Functor g, Functor f) => (a1 -> a) -> f (g a1) -> f (g a)
and use it on arbitrary nested functors.
>>> 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`
)
The Compose
constructor composes the []
and Maybe
type constructors into a new type constructor:
>>> :k Compose
Compose :: (k1 -> *) -> (k -> k1) -> k -> *
Compare:
Compose :: (k1 -> *) -> (k -> k1) -> k -> *
(.) :: (b -> c) -> (a -> b) -> a -> c
(The main difference is that (.)
can compose any two functions; it appears that Compose
and its associated instances require the last higher-kinded value to be applied to a concrete type, one of kind *
.)
Applying the data constructor Compose
to a list-of-Maybes produces a wrapped value
>>> :t Compose [Nothing]
Compose [Nothing] :: Compose [] Maybe a
As long as the arguments to Compose
are themselves instances of Functor
(as []
and Maybe
are), Compose f g
is also a functor, so you can use fmap
:
>>> fmap (+5) (Compose [Just 1,Nothing,Just 2,Just 3])
Compose [Just 6,Nothing,Just 7,Just 8]
The Compose
value is just a wrapper around the original value:
>>> getCompose $ fmap (+5) (Compose [Just 1,Nothing,Just 2,Just 3])
[Just 6,Nothing,Just 7,Just 8]
In the end, this isn't much different than simply composing fmap
directly:
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose x) = Compose (fmap (fmap f) x)
The difference is that you can define the type with a function and get its accompanying Functor
instance for free, instead of having to hand-code both.
回答3:
You're likely already aware of
map :: (a -> b) -> [a] -> [b]
...which is really just a special case of
fmap :: Functor f => (a -> b) -> f a -> f b
The latter works both on lists (where it behaves exactly like map
) and on Maybe
, because both are functors. I.e., both of the following signatures are valid specialisations:
fmap :: (a -> b) -> [a] -> [b]
fmap :: (a -> b) -> Maybe a -> Maybe b
Now, your use case looks similar, but unfortunately [Maybe a]
is not by itself a specialisation of f a
, rather it has the form f (g a)
. But note that we can just substitute the variable α
for g a
, i.e. for Maybe a
, then we can use
fmap :: (α -> β) -> [α] -> [β]
i.e.
fmap :: (Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b]
That looks even more like the signature you want! However we still need a function with signature Maybe a -> Maybe b
. Well, look above... I repeat:
fmap :: (a -> b) -> Maybe a -> Maybe b
This can be partially applied, i.e. when you have a function φ :: a -> b
, you can easily obtain the function fmap φ :: Maybe a -> Maybe b
. And that solves your problem:
maybeX :: (a -> b) -> [Maybe a] -> [Maybe b]
maybeX φ = fmap (fmap φ)
...or, if you want to get extra fancy,
maybeX = fmap . fmap
来源:https://stackoverflow.com/questions/47498896/apply-function-on-maybe-types