Related
Consider this liftA2 function:
liftA2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
liftA2 f Nothing Nothing = Nothing
liftA2 f (Just x) Nothing = Nothing
liftA2 f Nothing (Just y) = Nothing
liftA2 f (Just x) (Just y) = f x y
This is equivalent to the real liftA2 function from Control.Applicative, but specialized for Maybe. (and also liftM2 from Control.Monad)
I'm looking for a cousin of that function, that works like this:
mystery :: (a -> a -> a) -> Maybe a -> Maybe b -> Maybe c
mystery f Nothing Nothing = Nothing
mystery f (Just x) Nothing = Just x
mystery f Nothing (Just y) = Just y
mystery f (Just x) (Just y) = Just (f x y)
The closest concept I'm aware of is <|>, but that discards the second value if there are two, whereas I would rather pass a function to combine them.
What is this mystery function called? What type class does it operate on? What terms can I google to learn more? Thank you!
If you are willing to accept a different type signature, working with a Semigroup instance instead of with an arbitrary function f, then what you are looking for is the Option newtype from Data.Semigroup:
Prelude Data.Semigroup> Option Nothing <> Option Nothing
Option {getOption = Nothing}
Prelude Data.Semigroup> Option (Just [1]) <> Option Nothing
Option {getOption = Just [1]}
Prelude Data.Semigroup> Option Nothing <> Option (Just [2])
Option {getOption = Just [2]}
Prelude Data.Semigroup> Option (Just [1]) <> Option (Just [2])
Option {getOption = Just [1,2]}
For an arbitrary function you need something that is pretty specialized to Maybe - I don't really see how it could work for an arbitrary Applicative, or Alternative.
If I understand you correctly, you may be interested in the Semialign class from Data.Align, which offers zip-like operations that don't drop missing elements:
class Functor f => Semialign f where
align :: f a -> f b -> f (These a b)
align = alignWith id
alignWith :: (These a b -> c) -> f a -> f b -> f c
alignWith f as bs = f <$> align as bs
You can write
alignBasic :: Semialign f => (a -> a -> a) -> f a -> f a -> f a
alignBasic f = alignWith $ \case
This a -> a
That a -> a
These a1 a2 -> f a1 a2
-- or just
alignBasic f = alignWith (mergeThese f)
Since Maybe is an instance of Semialign, alignBasic can be used at type
alignBasic :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
Here goes an extra argument in support of alignWith being the liftA2 analogue you are looking for, as dfeuer suggests.
Using the monoidal presentation of Applicative...
fzip :: Applicative f => f a -> f b -> f (a, b)
fzip u v = (,) <$> u <*> v -- Think of this as a default implementation.
... we can do a subtle tweak on liftA2:
liftA2' :: Applicative f => ((a, b) -> c) -> f a -> f b -> f c
liftA2' f u v = f <$> fzip u v
This variation is relevant here because it translates straightforwardly to Alternative, under the products-to-sums monoidal functor interpretation:
-- fzip analogue. Name borrowed from protolude.
eitherA :: Alternative f => f a -> f b -> f (Either a b)
eitherA u v = (Left <$> u) <|> (Right <$> v)
-- Made-up name.
plusWith :: Alternative f => (Either a b -> c) -> f a -> f b -> f c
plusWith f u v = f <$> eitherA u v
plusWith, however, isn't helpful in your case. An Either a b -> c can't produce a c by combining an a with a b, except by discarding one of them. You'd rather have something that takes a These a b -> c argument, as using These a b can express the both-a-and-b case. It happens that a plusWith that uses These instead of Either is very much like alignWith:
-- Taken from the class definitions:
class Functor f => Semialign f where
align :: f a -> f b -> f (These a b)
align = alignWith id
alignWith :: (These a b -> c) -> f a -> f b -> f c
alignWith f a b = f <$> align a b
class Semialign f => Align f where
nil :: f a
Alternative is a class for monoidal functors from Hask-with-(,) to Hask-with-Either. Similarly, Align is a class for monoidal functors from Hask-with-(,) to Hask-with-These, only it also has extra idempotency and commutativity laws that ensure the unbiased behaviour you are looking for.
One aspect of Align worth noting is that it is closer to Alternative than to Applicative. In particular, the identity of the These tensor product is Void rather than (), and accordingly nil, the identity of align, is an empty structure rather than a singleton. That might come as a surprise, given that align and friends offer us greedy zips and zips with padding, and that zip is often thought of as an applicative operation. In this context, I feel it might help to mention a fact about ZipList. ZipList offers a zippy Applicative instance for lists in lieu of the default, cartesian product one:
GHCi> fzip [1,2] [3,9,27]
[(1,3),(1,9),(1,27),(2,3),(2,9),(2,27)]
GHCi> getZipList $ fzip (ZipList [1,2]) (ZipList [3,9,27])
[(1,3),(2,9)]
What is less widely known is that ZipList also has a different Alternative instance:
GHCi> [1,2] <|> [3,9,27]
[1,2,3,9,27]
GHCi> [3,9,27] <|> [1,2]
[3,9,27,1,2]
GHCi> getZipList $ ZipList [1,2] <|> ZipList [3,9,27]
[1,2,27]
GHCi> getZipList $ ZipList [3,9,27] <|> ZipList [1,2]
[3,9,27]
Instead of concatenating the lists, it pads the first list with a suffix of the second one to the larger of the two lengths. (It is a fitting instance for the type because it follows the left distribution law .) align for lists is somewhat similar to (<|>) #ZipList, except that it isn't biased towards either list:
GHCi> align [1,2] [3,9,27]
[These 1 3,These 2 9,That 27]
GHCi> align [3,9,27] [1,2]
[These 3 1,These 9 2,This 27]
Looking at Haskell's bind:
Prelude> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
I was confused by the following example:
Prelude> let same x = x
Prelude> [[1]] >>= \x -> same x
[1]
Looking at >>='s signature, how does \x -> same x type check with a -> m b?
I would've expected \x -> same x to have produced a [b] type, since the Monad m type here is [], as I understand.
You say
I would've expected \x -> same x to have produced a [b] type, since the Monad m type here is [], as I understand.
and so it does because it is.
We have
[[1]] >>= \ x -> same x
=
[[1]] >>= \ x -> x
[[Int]] [Int] -> [Int] :: [Int]
[] [Int] [Int] -> [] Int :: [] Int
m a a m b m b
Sometimes [] is describing a kind of "nondeterminism" effect. Other times, [] is describing a container-like data structure. The fact that it's difficult to tell the difference between which of these two purposes is being served is a feature of which some people are terribly proud. I'm not ready to agree with them, but I see what they're doing.
Looking at >>='s signature, how does \x -> same x type check with a -> m b?
It's actually very simple. Look at the type signatures:
same :: x -> x
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(>>= same) :: Monad m => m a -> (a -> m b) -> m b
|________|
|
x -> x
Therefore:
x := a
-- and
x := m b
-- and by transitivity
a := x := m b
-- or
a := m b
Hence:
(>>= same) :: Monad m => m (m b) -> m b
This is just the join function from the Control.Monad module, and for the list monad it is the same as the concat function. Thus:
[[1]] >>= \x -> same x
-- is the same as the following via eta reduction
[[1]] >>= same
-- is the same as
(>>= same) [[1]]
-- is the same as
join [[1]]
-- is the same as
concat [[1]]
-- evaluates to
[1]
I would've expected \x -> same x to have produced a [b] type, since the Monad m type here is [], as I understand.
Indeed, it does. The \x -> same x function which has the type x -> x is specialized to the type [b] -> [b] as I explained above. Hence, (>>= same) is of the type [[b]] -> [b] which is the same as the concat function. It flattens a list of lists.
The concat function is a specialization of the join function which flattens a nested monad.
It should be noted that a monad can be defined in terms of either >>= or fmap and join. To quote Wikipedia:
Although Haskell defines monads in terms of the return and >>= functions, it is also possible to define a monad in terms of return and two other operations, join and fmap. This formulation fits more closely with the original definition of monads in category theory. The fmap operation, with type Monad m => (a -> b) -> m a -> m b, takes a function between two types and produces a function that does the “same thing” to values in the monad. The join operation, with type Monad m => m (m a) -> m a, “flattens” two layers of monadic information into one.
The two formulations are related as follows:
fmap f m = m >>= (return . f)
join n = n >>= id
m >>= g ≡ join (fmap g m)
Here, m has the type Monad m => m a, n has the type Monad m => m (m a), f has the type a -> b, and g has the type Monad m => a -> m b, where a and b are underlying types.
The fmap function is defined for any functor in the category of types and functions, not just for monads. It is expected to satisfy the functor laws:
fmap id ≡ id
fmap (f . g) ≡ (fmap f) . (fmap g)
The return function characterizes pointed functors in the same category, by accounting for the ability to “lift” values into the functor. It should satisfy the following law:
return . f ≡ fmap f . return
In addition, the join function characterizes monads:
join . fmap join ≡ join . join
join . fmap return ≡ join . return = id
join . fmap (fmap f) ≡ fmap f . join
Hope that helps.
As a few people have commented, you've found a really cute property about monads here. For reference, let's look at the signature for bind:
:: Monad m => m a -> (a -> m b) -> m b
In your case, the type a === m b as you have a [[a]] or m (m a). So, if you rewrite the signature of the above bind operation, you get:
:: Monad m => m (m b) -> ((m b) -> m b) -> m b
I mentioned that this is cute, because by extension, this works for any nested monad. e.g.
:: [[b]] -> ([b] -> [b]) -> [b]
:: Maybe (Maybe b) -> (Maybe b -> Maybe b) -> Maybe b
:: Reader (Reader b) -> (Reader b -> Reader b) -> Reader b
If you look at the function that get's applied here, you'll see that it's the identity function (e.g. id, same, :: forall a. a -> a).
This is included in the standard libraries for Haskell, as join. You can look at the source here on hackage. You'll see it's implemented as bind id, or \mma -> mma >>= id, or (=<<) id
As you say m is []. Then a is [Integer] (ignoring the fact that numbers are polymorphic for simplicity's sake) and b is Integer. So a -> m b becomes [Integer] -> [Integer].
First: we should use the standard version of same, it is called id.
Now, let's rename some type variables
id :: (a'' ~ a) => a -> a''
What this means is: the signature of id is that of a function mapping between two types, with the extra constraint that both types be equal. That's all – we do not require any particular properties, like “being flat”.
Why the hell would I write it this way? Well, if we also rename some of the variables in the bind signature...
(>>=) :: (Monad m, a'~m a, a''~m b) => a' -> (a -> a'') -> a''
...then it is obvious how we can plug the id, as the type variables have already been named accordingly. The type-equality constraint a''~a from id is simply taken to the compound's signature, i.e.
(>>=id) :: (Monad m, a'~m a, a''~m b, a''~a) => a' -> a''
or, simplifying that,
(>>=id) :: (Monad m, a'~m a, m b~a) => a' -> m b
(>>=id) :: (Monad m, a'~m (m b)) => a' -> m b
(>>=id) :: (Monad m) => m (m b) -> m b
So what this does is, it flattens a nested monad to a single application of that same monad. Quite simple, and as a matter of fact this is one the “more fundamental” operation: mathematicians don't define the bind operator, they instead define two morphisms η :: a -> m a (we know that, it's return) and μ :: m (m a) -> m a – yup, that's the one you've just discovered. In Haskell, it's called join.
The monad here is [a] and the example is pointlessly complicated. This’ll be clearer:
Prelude> [[1]] >>= id
[1]
just as
Prelude> [[1]] >>= const [2]
[2]
i.e. >>= is concatMap and is concat when used with id.
In Using the Maybe Monad in “reverse” acfoltzer nicely shows how to use mplus. I want to have a similar effect but with the list of functions as a parameter:
tryFuncs :: [a -> Maybe b] -> a -> Maybe b
...
so a call like
tryFuncs [f, g, h] x
would become possible and do the same as
(f x) `mplus` (g x) `mplus` (h x)
How can one achieve this?
The simplest is to use msum (a list version of mplus) together with map:
tryFuncs fs x = msum $ map ($ x) fs
(In the end, this solution will be identical to Ørjan Johansen's answer, since Maybes MonadPlus is equivalent to the First Monoid's behaviour. It's a neat little application of the a -> b monoid though, which is easily overlooked.)
Conceptually, the function you're looking for is ... mconcat!
tryFuncs' :: Monoid b => [a -> Maybe b] -> a -> Maybe b
tryFuncs' = mconcat
Unfortunately, the default Monoid instance for Maybe is not what you want here ("ignore Nothing, mappend Just contents"), otherwise that solution would have been truly neat.
But there's the First wrapper around Maybe that gives you the "retain first Just" behaviour, so that
-- newtype First a = First (Maybe a)
tryFuncsFirst :: [a -> First b] -> a -> First b
tryFuncsFirst = mconcat
What's left for you is to wrap/unwrap the Maybes to Firsts.
firstify :: (a -> Maybe b) -> (a -> First b)
firstify f = First . f
firstifyList :: [a -> Maybe b] -> [a -> First b]
firstifyList = map firstify
getFirst :: First a -> Maybe a -- Defined in Data.Monoid
So now you can recover your desired function by wrapping-mconcat-unwrapping,
[a -> Maybe b] -> a -> Maybe b
tryFuncs fs x = getFirst (mconcat (firstifyList xs) x)
But how does this work? Well, there are two monoids at work here, First a and Monoid b => (a -> b), and the latter one is where the magic happens. To spell the instance out a little, using <> for mappend,
(a <> b) x = a x <> b c
-- and therefore
mconcat [a,b,c] x = mconcat [a x, b x, c x] -- (1)
So now the above code can be understood:
First-wrap all the input functions to take them from a -> Maybe b to a -> First b, which is the same, but has a different Maybe Monoid instance.
mconcat the list of functions, this uses the Monoid b => (a -> b) instance I just mentioned. All functions in the created list are applied to x, leaving you with a list of First b, which is then concatenated again, just as in (1).
Extract the resulting Maybe value out of the First wrapper again.
Here's the offending code (also on lpaste.net):
module Data.Graph.Dijkstra
( dijkstra
, dijkstraPath
) where
-- Graph library import
import Data.Graph.Inductive hiding (dijkstra)
-- Priority queue import
import qualified Data.PQueue.Prio.Min as PQ
-- Standard imports
import Data.List (find)
import Data.Maybe (fromJust)
import Data.Monoid
-- Internal routine implementing Dijkstra's shortest paths
-- algorithm. Deemed internal because it needs to be kickstarted with
-- a singleton node queue. Based on FGL's current implementation of
-- Dijkstra.
dijkstraInternal ::
(Graph gr, Ord b, Monoid b) => gr a b -> PQ.MinPQueue b [Node] -> [[Node]]
dijkstraInternal g q
| PQ.null q = []
| otherwise =
case match v g of
(Just cxt,g') -> p:dijkstraInternal g' (PQ.unions (q' : expand cxt minDist p))
(Nothing, g') -> dijkstraInternal g' q'
where ((minDist,p#(v:_)), q') = PQ.deleteFindMin q
expand (_,_,_,s) dist pathToC =
map (\(edgeCost,n) -> PQ.singleton (dist `mappend` edgeCost) (n:pathToC)) s
-- Given a graph and a start node, returns a list of lists of nodes
-- corresponding to the shortest paths from the start to all other
-- nodes, where the edge costs are accumulated according to the Monoid
-- instance of the edge label type and costs are compared by the edge
-- label's Ord instance.
dijkstra :: (Graph gr, Ord b, Monoid b) => gr a b -> Node -> [[Node]]
dijkstra g start = dijkstraInternal g (PQ.singleton `mempty` [start]) -- !!!
dijkstraPath :: (Graph gr, Ord b, Monoid b) => gr a b -> Node -> Node -> [LNode a]
dijkstraPath g start goal =
let paths = dijkstra g start
pathNodes = find ((goal ==) . head) paths -- Can paths be empty?
in
case pathNodes of
Nothing -> []
Just ps -> reverse $ map (\n -> (n, fromJust $ lab g n)) ps
The weirdness is in line 39, marked with the -- !!! comment. This code compiles, but the runtime error is that no matter what, the PQ.singleton function returns an empty priority queue. I realized I had accidentally added backticks to mempty, so when I removed those the code compiled and worked as expected.
This however struck me as strange. How could the code have correctly compiled with backticks around mempty, which is not a binary function at all (mempty :: a)?
After some very generous help on #haskell, I found that it had something to do with the Monoid instance for functions:
instance Monoid b => Monoid (a -> b)
I now have an extremely vague understanding of why this error successfully typechecked, but I still feel somehow morally wronged. Can someone explain exactly how this happened?
Additionally, I'd also like to direct attention to the priority queue's singleton function that I'm using: according to the source, it doesn’t return an empty queue. However, at line 24, that same priority queue immediately gets evaluated as being empty. (I verified this with trace calls.)
So, in general, the code:
a `f` b
is just syntactic sugar for:
f a b
Therefore your code became:
mempty PQ.singleton [start]
So the type-checker inferred the type for that particular mempty:
mempty :: (k -> a -> PQ.MinPQueue k a) -> [Node] -> PQ.MinPQueue b [Node]
You correctly found the right instance that is the problem. Anything of type a -> b is a Monoid, provided that b is. So let's bracket that type above:
mempty :: (k -> a -> PQ.MinPQueue k a) -> ([Node] -> PQ.MinPQueue b [Node])
So, that type can be a Monoid if [Node] -> PQ.MinPQueue b [Node] is a Monoid. And by the same logic, [Node] -> PQ.MinPQueue b [Node] can be a Monoid if PQ.MinPQueue b [Node] is one. Which it is. So the type-checker is fine with this code.
Presumably the implementation of our troublesome instance is:
instance Monoid => Monoid (a -> b) where
mempty = const mempty
So overall, you get an empty priority queue. So really, I think it comes down to a question of whether it was wise for the designers to include this instance at all. Its net effect is that any function returning a monoid can be a monoid, which should allow you to combine the results. The more useful case here is mappend, which can append two a -> b functions by applying them both and using mappend to combine the results. For example:
extremes = (return . minimum) `mappend` (return . maximum)
rather than:
extremes xs = [minimum xs, maximum xs]
Hmmm, maybe someone else can produce a sensible terser example.
So backticks turn a binary function into an infix operator, making
x `op` y
equivalent to
op x y
So op needs to be of type a -> b -> c where x :: a and y :: b.
In your case, op was mempty, with the type Monoid m => m. But we know it to be of the form a -> b -> c, so substitute that and you get (this is no longer valid syntax) Monoid (a -> b -> c) => a -> b -> c, because we can substitute that m for anything as long as the constraint holds.
Now we know (due to the instance declaration) that any function of the form s -> t, where t is a Monoid, is a Monoid itself, and we also know that a -> b -> c is really a -> (b -> c), i.e. a function taking one argument and returning another function. So if we substitute a for s and (b -> c) for t, the we fulfill the Monoid instance, if t is a Monoid. Of course, t is (b -> c), so we can apply the same Monoid instance again (with s = b and t = c), so if c is a Monoid, we're good.
So what is c? The expression you had was
PQ.singleton `mempty` [start]
i.e.
mempty PQ.singleton [start]
The instance declaration for Monoid (a -> b) defines mempty _ = mempty, i.e. it's a function that ignores its argument and returns the empty element of the b Monoid. In other words, we can expand the call above to
mempty [start]
i.e. we ignore the argument and use mempty of the inner Monoid (which is b -> c). Then we repeat, ignoring the argument again:
mempty
So the expression you had is just equivalent to a single mempty, which has the type Monoid c => c, i.e. it can be any Monoid whatsoever.
In your case, the larger expression deduces c to be a PQ.MinPQueue. And MinPQueue is a Monoid instance with mempty being the empty queue.
This is how you end up with the result you're seeing.
You've had a couple good answers here already, I thought I would just post this since it's a bit simpler and helped me as I was puzzling this out in ghci.
mempty :: (a -> b) = mempty _ = mempty So it's essentially const mempty.
λ> :t mempty :: (a -> b)
<interactive>:1:1:
No instance for (Monoid b) arising from a use of `mempty'
So b has to be a Monoid since we're asking for the mempty of that type, makes sense.
λ> :t mempty :: (a -> [b])
mempty :: (a -> [b]) :: a -> [b]
λ> :t mempty :: (a -> c -> [b])
mempty :: (a -> c -> [b]) :: a -> c -> [b]
We can recursively chain these. Since (->) is right associative (a -> b) may represent (a -> c -> d) when b == (c -> d). So we can supply an arbitrary number of arguments and the mempty for functions will be recursively applied until it's consumed all arguments.
λ> import Data.Map
λ> (mempty :: (a -> c -> Map Int Int)) 4 5
fromList []
λ> (mempty :: (a -> c -> d -> Map Int Int)) 4 5 6
fromList []
So we see that applying the function mempty will throw away any arguments it's given and return the mempty for whatever type is expected at the position the expression is in.
I want to map over Applicative form.
The type of map-like function would be like below:
mapX :: (Applicative f) => (f a -> f b) -> f [a] -> f [b]
used as:
result :: (Applicative f) => f [b]
result = mapX f xs
where f :: f a -> f b
f = ...
xs :: f[a]
xs = ...
As the background of this post, I try to write fluid simulation program using Applicative style referring to Paul Haduk's "The Haskell School of Expression", and I want to express the simulation with Applicative style as below:
x, v, a :: Sim VArray
x = x0 +: integral (v * dt)
v = v0 +: integral (a * dt)
a = (...calculate acceleration with x v...)
instance Applicative Sim where
...
where Sim type means the process of simulation computation and VArray means Array of Vector (x,y,z). X, v a are the arrays of position, velocity and acceleration, respectively.
Mapping over Applicative form comes when definining a.
I've found one answer to my question.
After all, my question is "How to lift high-order functions (like map
:: (a -> b) -> [a] -> [b]) to the Applicative world?" and the answer
I've found is "To build them using lifted first-order functions."
For example, the "mapX" is defined with lifted first-order functions
(headA, tailA, consA, nullA, condA) as below:
mapX :: (f a -> f b) -> f [a] -> f [b]
mapX f xs0 = condA (nullA xs0) (pure []) (consA (f x) (mapA f xs))
where
x = headA xs0
xs = tailA xs0
headA = liftA head
tailA = liftA tail
consA = liftA2 (:)
nullA = liftA null
condA b t e = liftA3 aux b t e
where aux b t e = if b then t else e
First, I don't think your proposed type signature makes much sense. Given an applicative list f [a] there's no general way to turn that into [f a] -- so there's no need for a function of type f a -> f b. For the sake of sanity, we'll reduce that function to a -> f b (to transform that into the other is trivial, but only if f is a monad).
So now we want:
mapX :: (Applicative f) => (a -> f b) -> f [a] -> f [b]
What immediately comes to mind now is traverse which is a generalization of mapM. Traverse, specialized to lists:
traverse :: (Applicative f) => (a -> f b) -> [a] -> f [b]
Close, but no cigar. Again, we can lift traverse to the required type signature, but this requires a monad constraint: mapX f xs = xs >>= traverse f.
If you don't mind the monad constraint, this is fine (and in fact you can do it more straightforwardly just with mapM). If you need to restrict yourself to applicative, then this should be enough to illustrate why you proposed signature isn't really possible.
Edit: based on further information, here's how I'd start to tackle the underlying problem.
-- your sketch
a = liftA sum $ mapX aux $ liftA2 neighbors (x!i) nbr
where aux :: f Int -> f Vector3
-- the type of "liftA2 neighbors (x!i) nbr" is "f [Int]
-- my interpretation
a = liftA2 aux x v
where
aux :: VArray -> VArray -> VArray
aux xi vi = ...
If you can't write aux like that -- as a pure function from the positions and velocities at one point in time to the accelerations, then you have bigger problems...
Here's an intuitive sketch as to why. The stream applicative functor takes a value and lifts it into a value over time -- a sequence or stream of values. If you have access to a value over time, you can derive properties of it. So velocity can be defined in terms of acceleration, position can be defined in terms of velocity, and soforth. Great! But now you want to define acceleration in terms of position and velocity. Also great! But you should not need, in this instance, to define acceleration in terms of velocity over time. Why, you may ask? Because velocity over time is all acceleration is to begin with. So if you define a in terms of dv, and v in terms of integral(a) then you've got a closed loop, and your equations are not propertly determined -- either there are, even given initial conditions, infinitely many solutions, or there are no solutions at all.
If I'm thinking about this right, you can't do this just with an applicative functor; you'll need a monad. If you have an Applicative—call it f—you have the following three functions available to you:
fmap :: (a -> b) -> f a -> f b
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
So, given some f :: f a -> f b, what can you do with it? Well, if you have some xs :: [a], then you can map it across: map (f . pure) xs :: [f b]. And if you instead have fxs :: f [a], then you could instead do fmap (map (f . pure)) fxs :: f [f b].1 However, you're stuck at this point. You want some function of type [f b] -> f [b], and possibly a function of type f (f b) -> f b; however, you can't define these on applicative functors (edit: actually, you can define the former; see the edit). Why? Well, if you look at fmap, pure, and <*>, you'll see that you have no way to get rid of (or rearrange) the f type constructor, so once you have [f a], you're stuck in that form.
Luckily, this is what monads are for: computations which can "change shape", so to speak. If you have a monad m, then in addition to the above, you get two extra methods (and return as a synonym for pure):
(>>=) :: m a -> (a -> m b) -> m b
join :: m (m a) -> m a
While join is only defined in Control.Monad, it's just as fundamental as >>=, and can sometimes be clearer to think about. Now we have the ability to define your [m b] -> m [b] function, or your m (m b) -> m b. The latter one is just join; and the former is sequence, from the Prelude. So, with monad m, you can define your mapX as
mapX :: Monad m => (m a -> m b) -> m [a] -> m [b]
mapX f mxs = mxs >>= sequence . map (f . return)
However, this would be an odd way to define it. There are a couple of other useful functions on monads in the prelude: mapM :: Monad m => (a -> m b) -> [a] -> m [b], which is equivalent to mapM f = sequence . map f; and (=<<) :: (a -> m b) -> m a -> m b, which is equivalent to flip (>>=). Using those, I'd probably define mapX as
mapX :: Monad m => (m a -> m b) -> m [a] -> m [b]
mapX f mxs = mapM (f . return) =<< mxs
Edit: Actually, my mistake: as John L kindly pointed out in a comment, Data.Traversable (which is a base package) supplies the function sequenceA :: (Applicative f, Traversable t) => t (f a) => f (t a); and since [] is an instance of Traversable, you can sequence an applicative functor. Nevertheless, your type signature still requires join or =<<, so you're still stuck. I would probably suggest rethinking your design; I think sclv probably has the right idea.
1: Or map (f . pure) <$> fxs, using the <$> synonym for fmap from Control.Applicative.
Here is a session in ghci where I define mapX the way you wanted it.
Prelude>
Prelude> import Control.Applicative
Prelude Control.Applicative> :t pure
pure :: Applicative f => a -> f a
Prelude Control.Applicative> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Prelude Control.Applicative> let mapX fun ma = pure fun <*> ma
Prelude Control.Applicative> :t mapX
mapX :: Applicative f => (a -> b) -> f a -> f b
I must however add that fmap is better to use, since Functor is less expressive than Applicative (that means that using fmap will work more often).
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
edit:
Oh, you have some other signature for mapX, anyway, you maybe meant the one I suggested (fmap)?