As it was mentioned in comments to your answer, there is similar story with join
and >>=
. When there're several semantically equivalent ways to define something it's better always to choose most efficient & pragmatic way. Haskell was designed to write code, not to prove things (though, somehow Haskell haven't still become very popular programming language unfortunately).
If starAp
had default implementation almost nobody would implement it (just as it happens now with >>
in Monad
type class). But <*>
is extremely useful operation. It is used in applicate & monadic parsers a lot (megaparsec
, attoparsec
, optparse-applicative
) and I can't imagine my life w/o liftA*
for joining things. And it is very important for this operation to be as efficient as possible. Implementing starAp
as fmap (uncurry ($)) (mult h x)
may bring hard times to inlining and optimizing things for compiler.
Moreover, representation of Applicative
using mult
and unit
operations doesn't really solves any problems. Obviously, mult = liftA2 (,)
. But your implementation with
mult (Just x) (Just y) = Just (x,y)
not fully correct. Because your implementation is not lazy enough. You will evaluate both cases when it may be enough to evaluate only one. So you still can mess up even with this simple function. Thus this representation is strictly worse.
(<*>)
presentation tends to be more convenient in day-to-day programming, even though themult
one is indeed neater in some aspects (e.g. the applicative laws look nicer when expressed in its terms). This scenario is similar to the relationship betweeen(>>=)
andjoin
. Relevant blog post: blog.ezyang.com/2012/08/applicative-functors – duplodeunit :: f ()
.pure
, then, can be recovered throughpure x = fmap (const x) unit
. – duplode