apply function on Maybe types?

我只是一个虾纸丫 提交于 2020-01-03 14:56:26

问题


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 Maybes, 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 as to get an f full of bs. For example, [] and Maybe are both Functors:

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 Maybes 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 fmaps" 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 fmaps 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!