When executing the IO action defined by someFun <$> (a :: IO ()) <$> (b :: IO ())
, is the execution of the a
and b
actions
Yes, the order is predefined by the Monad-Applicative correspondence. This is easy to see: The (*>)
combinator needs to correspond to the (>>)
combinator in a well-behaved Applicative
instance for a monad, and its definition is:
a *> b = liftA2 (const id) a b
In other words, if b
were executed before a
, the Applicative
instance would be ill-behaving.
Edit: As a side note: This is not explicitly specified anywhere, but you can find many other similar correspondences like liftM2
= liftA2
, etc.
It's certainly deterministic, yes. It will always do the same thing for any specific instance. However, there's no inherent reason to choose left-to-right over right-to-left for the order of effects.
However, from the documentation for Applicative:
If
f
is also aMonad
, it should satisfypure
=return
and(<*>)
=ap
(which implies thatpure
and<*>
satisfy the applicative functor laws).
The definition of ap
is this, from Control.Monad
:
ap :: (Monad m) => m (a -> b) -> m a -> m b
ap = liftM2 id
And liftM2
is defined in the obvious way:
liftM2 f m1 m2 = do { x1 <- m1; x2 <- m2; return (f x1 x2) }
What this means is that, for any functor that is a Monad
as well as an Applicative
, it is expected (by specification, since this can't be enforced in the code), that Applicative
will work left-to-right, so that the do
block in liftM2
does the same thing as liftA2 f x y = f <$> x <*> y
.
Because of the above, even for Applicative
instances without a corresponding Monad
, by convention the effects are usually ordered left-to-right as well.
More broadly, because the structure of an Applicative
computation is necessarily independent of the "effects", you can usually analyze the meaning of a program independently of how Applicative
effects are sequenced. For example, if the instance for []
were changed to sequence right-to-left, any code using it would give the same results, just with the list elements in a different order.
For the IO Applicative, this is certainly the case. But check out the async package for an example of an Applicative where in f <$> a <*> b
the effects of a
and b
happen in parallel.