There are a lot of functions that accomplish the same thing, but using Applicative
vs Monad
definitions.
Some examples are:
(<*>)
vsap
pure
vsreturn
(*>)
vs(>>)
traverse
vsmapM
sequenceA
vssequence
liftA
vsliftM
liftA2
vsliftM2
- etc.
And for Alternative
vs MonadPlus
:
empty
vsmzero
(<|>)
vsmplus
In the scenario that prompted this question, I wanted to traverse
/mapM
where the Applicative
/Monad
is []
.
In general, when it is known that the result will be the same, is it better style to use the Applicative
ones or the Monad
ones? E.g., is one more readable than another, or maybe faster than the other?
Edit (in response being suggested as a duplicate): Personally, I find these to be equally readable. I am in no way asking about maintainability, as these can be used interchangeably, and preferring one over another would have no impact on the code structure. I'm really wondering if one is more idiomatic, or more efficient, than the other.
1 Answer 1
For both efficiency and generality, you should typically prefer the most general operators. So you should use Applicative
operators in preference to Monad
, and Alternative
to MonadPlus
.
The efficiency difference is often not significant, so if you already have a Monad
/MonadPlus
constraint, you should feel free to use any of the relevant operators to produce the nicest looking code.
The ApplicativeDo
extension can allow you to get the benefit of do
-notation without a Monad
requirement. I recommend the Haxl paper for an non-trivial example where there is a quite significant performance difference between Applicative
and Monad
(and was a motivator for implementing ApplicativeDo
notation).
-
1I agree with this answer, except that often, using the most general operator gives less efficiency, because the particular type may have special properties that make the algorithm faster for it (trivial example: built-in
[a]
versus length-tagged([a], Int)
have, respectively,O(n)
andO(1)
length functions).Ryan Reich– Ryan Reich2017年10月16日 17:52:06 +00:00Commented Oct 16, 2017 at 17:52 -
I'm not suggesting using the most general operator in all cases; I'm only suggesting it in the context of choosing between operations in the
Monad
,Applicative
,Functor
hierarchy and closely related classes. The idea is not to pay for something you don't use. In the general case, a less contrived example isnub
versusnubOrd
. For more elaborate functions built from theMonad
/Applicative
operations, the extra structure can allow for better algorithms, e.g. a generate-and-test algorithm where you filter as you go, but the implementation difference is more than swapping operators.Derek Elkins left SE– Derek Elkins left SE2017年10月16日 21:11:15 +00:00Commented Oct 16, 2017 at 21:11
Applicative
ones". In a hypothetically correctly-having-Applicative
-from-the-beginning Haskell, we'd only have(<*>)
,traverse
, etc. (Also:liftA
is the same asfmap
/(<$>)
, which is the real thing to use there.)