5

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.

schmittlauch
  • 127
  • 6
  • 2
    These should be equivalent. – Willem Van Onsem Jan 13 '20 at 14:18
  • 1
    @WillemVanOnsem That already answers the first half of the question, thanks. Still remaining: Which variant do people prefer and why? – schmittlauch Jan 13 '20 at 14:20
  • 1
    well the `<$>` is shorter, easier to understand, and might be faster (since first using `pure`, etc. makes it more "generic"). – Willem Van Onsem Jan 13 '20 at 14:21
  • There is more symmetry involving `$, *, pure`, namely `pure x <* fa == x <$ fa` (similarly for `*>` and `$>` (defined in `Data.Functor`)). For example `7 <$ [1..3] == pure 7 <* [1..3] == [7, 7, 7]`. So to me the function version using `$` says the functor values is a pure functor value/a simple functor value. (Compare [7] which is a "simple functor value" with [7, 7, 7] which is not.) – Micha Wiedenmann Jan 13 '20 at 14:47

2 Answers2

9

This needs to be equivalent. Indeed, in the documentation of the Applicative typeclass, we read:

As a consequence of these laws, the Functor instance for f will satisfy

fmap 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 as), 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.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
5

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
    ]
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • `foo a b c d = id` Don't you think about the poor compiler that has to optimise out all the ids? Jokes aside, thanks for sharing these (partially crude) work arounds for Haskell indentation. – schmittlauch Jan 14 '20 at 12:05
  • @schmittlauch Sure, I think about the poor compiler! For all but the last, I've checked that GHC is smart enough to emit the same code for the orthodox and dmwit versions. (The last one is only exempt because I thought of it in the shower yesterday after writing a first draft of this and haven't had a chance to spend some quality time with my compiler since!) – Daniel Wagner Jan 14 '20 at 15:13