20

I understand the reasoning behind <$>'s type signature, as it's just an infix version of fmap, but comparing it to >>='s type signature it makes a lot less sense to me.

Let's first establish what I mean by that.

(>>=) :: Monad m => m a -> (a -> m b) -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(<$>) :: Functor f => (a -> b) -> f a -> f b

Looking at the type signatures we can see that >>= takes a value on the left, and a function on the right, which makes a lot of sense if you consider its chaining property: foo >>= bar >>= baz

Which leads me to wonder, why don't <*> and <$> do that too? you can't write foo <*> bar <*> baz because it would require the output of foo <*> bar to be a function, not a value.

I know that <**> and =<< exist that both flip the order of the parameters, allowing me to do something like:

Just 4 <**> pure (+3) <**> pure (*2) >>= (\x -> Just (x-3)) 

Which could have been beautifully reduced to:

Just 4 <$$> (+3) <$$> (*2) >>= (\x -> Just (x-3))

If <$$> had existed, or if the parameter order of <$> and <*> had been reversed.

Another thing that makes me wonder why the difference exists, is that it makes it harder for newcomers to get used to and/or remember whether it's the function, or the value that comes first, without having to look it up.

So why is it that in the cases of <*> and <$> it's fn op val but with >>= it's val op fn?

Electric Coffee
  • 11,733
  • 9
  • 70
  • 131
  • 4
    but you often want exactly this with `Applicatives`: for example `(+) <$> Just 5 <*> Just 5 ` <- isn't that `Just` beautiful ;) – Random Dev Sep 24 '14 at 11:27
  • 2
    `(>>=)` constructs chains "imperative style" (otherwise we'd be left wondering why it isn't the same order as `$`). `(<=<)` also allows chaining - "`(.)` style". `(<*>)` is useful for "applying" multi-argument functions - supplying the arguments in the order of declaration. – Sassa NF Sep 24 '14 at 11:31
  • 2
    The real question is why `(>>=)` is using an order opposite of pretty much everything in Haskell. The type of `(=<<) :: Monad m => (a -> m b) -> (m a -> m b)` looks much more like everything else in Haskell than that of `(>>=) = flip (=<<)`. –  Sep 25 '14 at 09:51
  • @Rhymoid I guess it's more natural to read left-to-right, so passing a value into a function left-to-right by the direction of the arrows feels more natural? `val >>= fun` – Electric Coffee Sep 25 '14 at 14:14
  • @ElectricCoffee Left-to-right, top-to-bottom is [not natural per se](http://en.wikipedia.org/wiki/File:Writing_directions_of_the_world.svg). In files, "early to late" is a more appropriate terminology. In that context, it seems to make sense to have composition work like `(>>>)` does. Even the type is nicer (restricted to functions, `(>>>) :: (a -> b) -> (b -> c) -> (a -> c)`). To make this more natural, you also need to change how function application works: `(foo >>> bar >>> baz) x == baz (bar (foo x))`. Are you sure you want to write `x (foo >>> bar >>> baz) == ((x foo) bar) baz` instead? –  Sep 25 '14 at 14:31
  • @ElectricCoffee Because in that case, you also get weird things like `z y x (\x y z -> ...)`. I'm not entirely sure if *mirroring* the arguments w.r.t. to the patterns *and the type* is desirable. There'll always be something unnatural in functional languages. I think having 'reversed' function composition isn't too much of a burden compared to the alternatives. –  Sep 25 '14 at 14:33

2 Answers2

22

Don't let monads get in the way here. Think about application.

Compare:

(<$>) :: Functor f => (a -> b) -> f a -> f b

and

($) :: (a -> b) -> a -> b

You can see there is a connection between regular application and application under a functor.

Trivia: there have been proposals to use brackets to overload whitespace (application) so that we can write:

(| f a1 .. an |)

instead of

pure f <*> a1 <*> .. <*> an
Don Stewart
  • 137,316
  • 36
  • 365
  • 468
  • 4
    I never saw it like that, I only looked at `<$>` in terms of `fmap`, now that you've presented it as a variant of `$` it makes a lot more sense – Electric Coffee Sep 24 '14 at 12:35
18

The answer to "why does it take parameters in this order" is basically "because". Whoever defined these functions thought that was the best way. (And it probably wasn't the same person in each case.) I will, however, offer some examples:

Suppose we have a parsing monad of some kind. Suppose also that we have defined

 data Foobar = Foobar X Y Z

 parseFoo :: Parser X
 parseBar :: Parser Y
 parseBaz :: Parser Z

Then we can write

 parseFoobar :: Parser Foobar
 parseFoobar = do
   foo <- parseFoo
   bar <- parseBar
   baz <- parseBaz
   return (Foobar foo bar baz)

Or, explicitly,

parseFoobar =
  parseFoo >>= \ foo ->
  parseBar >>= \ bar ->
  parseBaz >>= \ baz ->
  return (Foobar foo bar baz)

Now let's write that applicative-style:

parseFoobar = return Foobar <*> parseFoo <*> parseBar <*> parseBaz

or, alternatively,

parseFoobar = Foobar <$> parseFoo <*> parseBar <*> parseBaz

If we suppose that <**> = flip <*> exists (and has the correct associativity), then we have

parseFoobar = parseBaz <**> parseBar <**> parseFoo <**> return Foobar

That just looks strange. With the function at the end, and the arguments in reverse order? Why would you want to write it that way? (Note that any effects are also in reverse order.)

In the monadic version, effects happen top-to-bottom. In the applicative version, effects happen left-to-right. That seems "natural".

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • can you add some example types to the example functions? just so I have an easier time wrapping my head around what's happening – Electric Coffee Sep 24 '14 at 11:45
  • Edited a little stuff in. – MathematicalOrchid Sep 24 '14 at 11:55
  • How does this work in terms of function chaining though? what you're doing here is applying a function to 3 wrapped values, but `Just 4 >>= foo >>= bar >>= baz`, works significantly differently from say `(+) <$> Just 3 <*> Just 4` – Electric Coffee Sep 24 '14 at 12:21
  • Rather than three "wrapped values", it's actually three _parsers_ that parse some input and produce values as output. In both versions, you're executing effectful computations (parser) that produce outputs, and then combining those outputs into a datastructure (`Foobar`). – MathematicalOrchid Sep 24 '14 at 14:03