[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.