After trying out examples for a while, to me it looks like myFunction <$>
and pure myFunction <*>
are equivalent when working on the Control.Applicative type class.
Example:
(++) <$> Just "foo" <*> Just "bar"
pure (++) <*> Just "foo" <*> Just "bar"
both yield Just "foobar"
.
Are these indeed equivalent or am I overlooking an edge case?
Which variant shall I prefer/ is more common? While the pure
approach is longer, it looks more general and truthful to the Control.Applicative typclass to me.
This needs to be equivalent. Indeed, in the documentation of the Applicative
typeclass, we read:
As a consequence of these laws, the
Functor
instance forf
will satisfyfmap f x = pure f <*> x
Since the (<$>) :: Functor f => (a -> b) -> f a -> f b
is an:
An infix synonym for
fmap
.
It thus holds that:
f <$> x = pure f <*> x
The two can thus be used to achieve the same. Since f <$> x
is however shorter, and might be faster (since (<*>)
needs to deal with all f a
s), it is probably advisable to use (<$>)
). Furthermore as @chepner says, the default implementation of liftA2
is liftA2 f x = (<*>) (fmap f x)
, so this uses fmap
(so <$>
) as well.
I'd just like to chime in briefly here, because I hold many unpopular opinions and this is one of them and you asked so nyaaaaah.
Admitted: the community appears to more or less agree that f <$> x <*> y <*> z
style is better. But I actually prefer pure f <*> x <*> y <*> z
. I find it relatively common that lines written in applicative style tend towards being longish, since each argument is often itself a call to a function, so:
fancyParser = FancyConstructor <$> fooParserWith 7 blag <*> barParserSep "hi" (sizzleParser pop) <*> bazParser
-- OR
fancyParser = pure FancyConstructor <*> fooParserWith 7 blag <*> barParserSep "hi" (sizzleParser pop) <*> bazParser
For readability, I often split the arguments onto their own lines; I find the visual separation makes it clearer where the argument boundaries are, gives my eyes a bit of a rest, and makes it less likely that the line will wrap in an ugly way:
fancyParser = FancyConstructor
<$> fooParserWith 7 blag
<*> barParserSep "hi" (sizzleParser pop)
<*> bazParser
-- OR
fancyParser = pure FancyConstructor
<*> fooParserWith 7 blag
<*> barParserSep "hi" (sizzleParser pop)
<*> bazParser
In this form, I think it's quite clear why I prefer pure
/<*>
: it makes for a completely consistent line beginning. This consistency is visually appealing, for one. But more importantly, when I inevitably refactor FancyConstructor
to rearrange the order of fields, or insert a new field at the beginning, I can use line-wise editing commands with complete impunity. Certain regex searches become slightly easier as well. It only eliminates a little bit of friction... but eliminating small frictions is one important part of doing programming in the large.
P.S. Other unpopular examples of ways to get consistent line formats that I've found handy:
-- orthodox
foo a b c d
= f
. g
. h
-- dmwit
foo a b c d = id
. f
. g
. h
-- orthodox
foo a b c d =
[ x
, y
, z
]
-- dmwit
foo a b c d = tail [undefined
, x
, y
, z
]
-- orthodox
bad = die
$ "some kind of "
<> "error that is best described "
<> "on multiple lines"
-- dmwit
bad = die $ mempty {- or "", if appropriate -}
<> "some kind of "
<> "error that is best described "
<> "on multiple lines"
-- orthodox
foo
:: arg1
-> arg2
-> result
-- dmwit
foo ::
arg1 ->
arg2 ->
result
-- orthodox
foo a b c d =
[ t
| u <- v
, w <- x
, y <- z
]
-- dmwit
foo a b c d =
[ t | _ <- [()]
, u <- v
, w <- x
, y <- z
]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With