3

So that my code looks similar when using monads to when I use applicatives and functors I have been using =<< instead of >>=. I have also been using <* instead of *> / >>. However I have noticed that <* is NOT to *> what =<< is to >>=.

For example:

print 5 <* print 6

gives me:

5
6

When I was expecting them to print the other way around, with print 6 being called, printing 6, and then the result () getting thrown away and then calling print 5.

After further playing around it seems like <* and *> differ in only the return values, not in things like how the functions are combined / sequenced. Whereas =<< is a proper flip of >>=, including a switch in the fixity from left to right.

I was wondering why this was the case (it seems like >>= is backwards, but it somehow fits better with the, not backwards, <*> <* *> combination, even though the type signatures of >>= and <*> are flipped). I was also wondering if there was a << style operator I could / should use.

As a side question, when does left vs right associativity even make a difference when it comes to =<<, it seems as though no matter where you put the parenthesis, the result is the same, although I admittedly only tested with fairly simple examples.

semicolon
  • 2,530
  • 27
  • 37

1 Answers1

2

Aye, *> and <* only differ in their return value.

it somehow fits better with the, not backwards, <*> <* *> combination, even though the type signatures of >>= and <*> are flipped

I wouldn't say the signatures of >>= and <*> are flipped. Rather, <*> is just specialised to contain a function in its left argument, with is then applied to the value in the right argument. You can do this with >>= too, consider

fs<*>xs ≡ fs >>= \f -> f<$>xs

I don't think there's much need for an applicative-equivalent of <<, like there is no need for a flipped version of ++: just order the arguments of *> or <* in the order you need for your desired semantics!

does left vs right associativity even make a difference when it comes to =<<, it seems as though no matter where you put the parenthesis, the result is the same

Er, that's not correct. The version with parens on the right is not even well-typed. Perhaps you've tried some example with lambdas, and didn't take these into account with your parens?

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • "just order the arguments of `*>` or `<*` in the order you need for your desired semantics!". Couldn't the same be said about the need for `=<<` and `>>=`? Also for the associativity thing I meant like `(pure =<< pure) =<< pure 5` vs `pure =<< (pure =<< pure 5)`. – semicolon Apr 07 '16 at 22:43
  • For infix combinators with function arguments you need to consider that these are often used with lambdas (which gives a natural left-to-right direction) or point-free composition chains (right-to-left), so here either direction might have a clear benefit. But that's not relevant for `*>`, like it isn't for `++`. — Rg. associativity, careful: the right-parenthesized expression only is possible at all because there happens to be a function monad instance. The `pure`s live in different monads! – leftaroundabout Apr 07 '16 at 23:51
  • If either direction works, why not have operators for both? Also doesn't Haskell generally go more right to left, with it being a functional language and all, and with `$` `.` and simply ` `. It just seems strange to me how monad flips everything around, and it seems like applicative sort of partially does as well, `print =<< (++) <$> getLine <*> getLine` evaluates the first getLine first, whereas a series of `=<<` will get evaluated from right to left. – semicolon Apr 08 '16 at 05:07
  • @semicolon `<$>` and `<*>` come as sort-of substitutes for `$`, which *is* left-to-right (that's why it's called *applicative*). [I agree on the flipped order](https://stackoverflow.com/questions/34545818/is-monad-bind-operator-closer-to-function-composition-chaining-or-functi/34561605#34561605) of arguments. (also, [this](http://stackoverflow.com/questions/11234632/monads-with-join-instead-of-bind/11249714#11249714) too was illuminating, for me). – Will Ness Apr 08 '16 at 05:31
  • @WillNess how in the world is `$` left-to-right? `sum $ take 10 $ fmap (+ 5) $ repeat 3` to me seems like you start with `repeat 3` then `fmap (+ 5)` to it, then... and so on. – semicolon Apr 08 '16 at 06:49
  • 1
    @semicolon the function is consulted first. not all functions demand their argument(s)' value(s) immediately, or at all. cf. e.g. `take 1 ( (1:) $ undefined )`. – Will Ness Apr 08 '16 at 15:43
  • 1
    @semicolon `sum $ take 10 $ fmap (+ 5) $ repeat 3` is right-_associative_, but the evaluation order indeed starts left-to-right. – leftaroundabout Apr 08 '16 at 15:50
  • @WillNess ok sure, from an operational standpoint that is fair enough. But from a denotational standpoint `$` seems very much right to left, and I don't get why Haskell breaks that partially with Applicatives, and completely with Monad's, instead going totally right to left (denotationally). – semicolon Apr 08 '16 at 18:18
  • 1
    @semicolon: I'm not sure it makes much sense to talk about any “denotational direction” in Haskell. Consider `('H':)$('e':)$('l':)$('l':)$('o':)$"World"`. Is that not left-to-right? – leftaroundabout Apr 08 '16 at 20:00
  • @leftaroundabout absolutely not. It is right-to-left. You start with "World", prepend 'o' to get "oWorld", prepend 'l' to get "loWorld" etc. This is further emphasized by the fact that `$` is `infixr 0`. – semicolon Apr 08 '16 at 20:13
  • Well, what about `(++(++(++(++(++"World") "o")"l")"l")"e")"H"` then? There is no really meaningful way argue what you “start with”, unless you _define_ it by some structured type like an applicative, or by evaluation order. – leftaroundabout Apr 08 '16 at 20:21
  • How about `take 2 $ ('a':) $ ('b':) $ (error "This is never reached":) $ (error "Neither is this":) $ []`? You get back `"ab"` with no error. Haskell evaluation is outside-in as opposed to inside-out (strict languages are generally inside-out): You can take out the `$`s and convert this to `take 2 ('a':('b':(error "This is never reached":(error "Neither is this":[]))))`. Next the outermost function (`take 2`) is evaluated to see what it needs from its arguments before the arguments are evaluated. If it went right to left here there is no way it could have known not to throw those errors. – David Young Apr 09 '16 at 16:46