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!
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 ofa
andb
happen in parallel.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-behavedApplicative
instance for a monad, and its definition is:In other words, if
b
were executed beforea
, theApplicative
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
:The definition of
ap
is this, fromControl.Monad
:And
liftM2
is defined in the obvious way:What this means is that, for any functor that is a
Monad
as well as anApplicative
, it is expected (by specification, since this can't be enforced in the code), thatApplicative
will work left-to-right, so that thedo
block inliftM2
does the same thing asliftA2 f x y = f <$> x <*> y
.Because of the above, even for
Applicative
instances without a correspondingMonad
, 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 howApplicative
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.