In my opinion
<$>
and<*>
makes the code more FP than IO.
Haskell is not a purely functional language because that "looks better". Sometimes it does, often it doesn't. The reason for staying functional is not its syntax but its semantics. It equips us with referential transparency, which makes it far easier to prove invariants, allows very high-level optimisations, makes it easy to write general-purpose code etc..
None of this has much to do with syntax. Monadic computations are still purely functional – regardless of whether you write them with do
notation or with <$>
, <*>
and >>=
, so we get Haskell's benefits either way.
However, notwithstanding the aforementioned FP-benefits, it is often more intuitive to think about algorithms from an imperative-like point of view – even if you're accustomed to how this is implemented through monads. In these cases, do
notation gives you this quick insight of "order of computation", "origin of data", "point of modification", yet it's trivial to manually desugar it in your head to the >>=
version, to grasp what's going on functionally.
Applicative style is certainly great in many ways, however it is inherently point-free. That is often a good thing, but especially in more complex problems it can be very helpful to give names to "temporary" variables. When using only "FP" Haskell syntax, this requires either lambdas or explicitly named functions. Both have good use cases, but the former introduces quite a bit of noise right in the middle of your code and the latter rather disrupts the "flow" since it requires a where
or let
placed somewhere else from where you use it. do
, on the other hand, allows you to introduce a named variable right where you need it, without introducing any noise at all.