When executing the IO action defined by someFun <$> (a :: IO ()) <$> (b :: IO ())
, is the execution of the a
and b
actions ordered? That is, can I count on that a
is executed before b
is?
For GHC, I can see the IO is implemented using State, and also see here that it is an Applicative instance, but can't find the source of the actual instance declaration. Being implemented through State suggests that different IO effects need to be sequential, but doesn't necessary defines their ordering.
Playing around in GHCi seems that Appliative retains effect order, but is that some universal guarantee, or GHC specific? I would be interested in details.
import System.Time
import Control.Concurrent
import Data.Traversable
let prec (TOD a b) = b
fmap (map prec) (sequenceA $ replicate 5 (threadDelay 1000 >> getClockTime))
[641934000000,642934000000,643934000000,644934000000,645934000000]
Thanks!
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With