Description
Alternative has this distributivity law:
(f <|> g) <*> x == (f <*> x) <|> (g <*> x)
However, this law rules out a few instances, most notably "effectful" ones like Effect or Aff. For example:
f :: Aff (Unit -> Unit)
f = pure identity
g :: Aff (Unit -> Unit)
g = pure identity
x :: Aff Unit
x = log "hi" *> empty
for which (f <|> g) <*> x
will log "hi" once, whereas (f <*> x) <|> (g <*> x)
will log "hi" twice.
Note that Effect has no Alt instance right now (and therefore no Plus or Alternative either), whereas Aff does have all of those instances.
I think it would be good to decide whether we want to keep this law and say that the problem lies with Aff's instance, or condone the Aff instance by dropping the distributivity law from the Alternative class.
It would be useful to see a concrete example of a case where the distributivity law is useful for ensuring that something behaves sensibly, because I'm not aware of any (and I think Aff's instance is used quite widely in practice, and I'm not aware of this having had any real consequences).
If we did drop the Alternative distributivity law, we'd be left with just the annihilation law, empty <*> f = empty
. I think this makes sense as a class: I'd argue it gives you exactly what you need to write a sensible version of guard
, since the annihilation law ensures that subsequent effects after a failed guard
are nullified, and you need pure
from Applicative and empty
from Plus (see also #62).