Apply function on Maybe?

New to Haskell, and I can't figure out how to apply the function (a → b) in the [Maybe a] list and get [Maybe b]

maybeX:: (a -> b) -> [Maybe a] -> [Maybe b] 

The function should do the same as the map, apply the function f in the Maybe statement list, and if it just returns me f Just, and if it's Nothing, just nothing. As in the following example, I want to add +5 to each element of the following list:

 [Just 1,Just 2,Nothing,Just 3] 

and get

 [Just 6,Just 7,Nothing,Just 8] 

Actually I’m trying to understand this, and I tried a lot, but it always seems to me that this is something that I don’t know how it works. Thank you for your help!

+5
source share
3 answers

Let's start by defining how to act on one Maybe , and then we scale it to a whole list.

 mapMaybe :: (a -> b) -> Maybe a -> Maybe b mapMaybe f Nothing = Nothing mapMaybe f (Just x) = Just (fx) 

If the Maybe value contains a value, mapMaybe applies f to it, and if it does not contain a value, we simply return an empty Maybe .

But we have a Maybe s list, so we need to apply mapMaybe to each of them.

 mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b] mapMaybes f ms = [mapMaybe fm | m <- ms] 

Here I use list comprehension to evaluate mapMaybe fm for each m in ms .


Now for a more advanced technique. A template for applying a function to each value in a container is captured by a class of type Functor .

 class Functor f where fmap :: (a -> b) -> fa -> fb 

Type f is Functor if you can write a function that takes a function from a to b , and applies this function to f , complete a , to get f complete b s. For example, [] and Maybe are Functor s:

 instance Functor Maybe where fmap f Nothing = Nothing fmap f (Just x) = Just (fx) instance Functor [] where fmap f xs = [fx | x <- xs] 

Maybe version of fmap same as mapMaybe I wrote above, and the implementation of [] uses list comprehension to apply f to each element in the list.

Now, to write mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b] , you need to control each element in the list using the [] version of fmap , and then work with a separate Maybe using Maybe version fmap .

 mapMaybes :: (a -> b) -> [Maybe a] -> [Maybe b] mapMaybes f ms = fmap (fmap f) ms -- or: mapMaybes = fmap . fmap 

Note that we are actually invoking two different fmap implementations. External - fmap :: (Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b] , which is sent to the [] Functor instance. Inner - (a -> b) -> Maybe a -> Maybe b .


Another addition is that it’s quite esoteric, so don’t worry if you don’t understand everything here. I just want to give you a taste of something that I think is pretty cool.

This " fmap s style chain" ( fmap . fmap . fmap ... ) is a fairly common trick for drilling multiple layers of a structure. Each fmap is of type (a -> b) -> (fa -> fb) , so when you create them with (.) , You create a higher order function.

 fmap :: Functor g => (fa -> fb) -> (g (fa) -> g (fb)) fmap :: Functor f => (a -> b) -> (fa -> fb) -- so... fmap . fmap :: (Functor f, Functor g) => (a -> b) -> g (fa) -> g (fb) 

So, if you have a functor functor (functor ...), then n fmap will allow you to map elements at level n of the structure. Conall Elliot calls this style "semantic editor combinators . "

The trick also works with traverse :: (Traversable t, Applicative f) => (a -> fb) -> (ta -> f (tb)) , which is a kind of "spectacular fmap ".

 traverse :: (...) => (ta -> f (tb)) -> (s (ta) -> f (s (tb))) traverse :: (...) => (a -> fb) -> (ta -> f (tb)) -- so... traverse . traverse :: (...) => (a -> fb) -> s (ta) -> f (s (tb)) 

(I missed the bits before => because I ran out of horizontal space.) Therefore, if you have end-to-end tracing (traces ...), you can perform efficient computation on elements at level n just by writing traverse n times. Such workarounds are the main idea of ​​the lens library.

+11
source

[Note: this involves familiarity with functors.]

Another approach is to use composition at the type level 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 in the definition for maybeX :

 -- Wrap, map, and unwrap maybeX :: (a -> b) -> [Maybe a] -> [Maybe b] maybeX f = getCompose . fmap f . Compose 

(In fact, nothing in the definition implies Maybe or [] , just an annotation of a limited type. If you enable the FlexibleContexts extension, you can conclude (Functor g, Functor f) => (a1 -> a) -> f (g a1) -> f (ga) 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 (ga), since `Nothing :: Maybe a` 

)


The Compose constructor combines constructors of type [] and Maybe into a new type constructor:

 >>> :k Compose Compose :: (k1 -> *) -> (k -> k1) -> k -> * 

For comparison:

 Compose :: (k1 -> *) -> (k -> k1) -> k -> * (.) :: (b -> c) -> (a -> b) -> a -> c 

(The main difference is that (.) Can be any two functions: it turns out that Compose and its associated instances require the last higher value to be applied to a particular type, one of the types * .)

Applying Compose Data Designer to a Maybes List Creates a Wrapped Value

 >>> :t Compose [Nothing] Compose [Nothing] :: Compose [] Maybe a 

While Compose arguments themselves are instances of Functor (like [] and Maybe ), Compose fg 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 is not much different than just creating fmap directly:

 instance (Functor f, Functor g) => Functor (Compose fg) where fmap f (Compose x) = Compose (fmap (fmap f) x) 

The difference is that you can determine the type using the function and get your accompanying Functor instance for free, instead of manually specifying both.

+4
source

You probably already know about

 map :: (a -> b) -> [a] -> [b] 

... what really is a special case

 fmap :: Functor f => (a -> b) -> fa -> fb 

The latter works both in lists (where it behaves exactly like map ) and on Maybe , because both are functors. Ie, both of the following signatures are valid specializations:

 fmap :: (a -> b) -> [a] -> [b] fmap :: (a -> b) -> Maybe a -> Maybe b 

Now your use case is similar, but unfortunately, [Maybe a] is not a fa specialization in itself, rather it has the form f (ga) . But note that we can simply substitute the variable α for ga , i.e. For Maybe a , then we can use

 fmap :: (α -> β) -> [α] -> [β] 

i.e.

 fmap :: (Maybe a -> Maybe b) -> [Maybe a] -> [Maybe b] 

It looks like what you want! However, we still need a function with the signature Maybe a -> Maybe b . Well, see above ... I repeat:

 fmap :: (a -> b) -> Maybe a -> Maybe b 

This can be partially applied, i.e. when you have the function φ :: a -> b , you can easily get the function fmap φ :: Maybe a -> Maybe b . And this solves your problem:

 maybeX :: (a -> b) -> [Maybe a] -> [Maybe b] maybeX φ = fmap (fmap φ) 

... or, if you want more imagination,

 maybeX = fmap . fmap 
+2
source

Source: https://habr.com/ru/post/1273667/


All Articles