1

Consider the following function:

foo =
  [1,2,3] >>=
  return . (*2) . (+1)

For better readability and logic, I would like to move my pure functions (*2) and (+1) to the left of the return. I could achieve this like this:

infixr 9 <.
(<.) :: (a -> b) -> (b -> c) -> (a -> c)
(<.) f g = g . f

bar =
  [1,2,3] >>=
  (+1) <.
  (*2) <.
  return

However, I don't like the right-associativity of (<.).

Let's introduce a function leftLift:

leftLift :: Monad m => (a -> b) -> a -> m b
leftLift f = return . f

baz =
  [1,2,3] >>=
  leftLift (+1) >>=
  leftLift (*2) >>=
  return

I quite like this. Another possibility would be to define a variant of bind:

infixl 1 >>$
(>>$) :: Monad m => m a -> (a -> b) -> m b
(>>$) m f = m >>= return . f

qux =
  [1,2,3] >>$
  (+1) >>$
  (*2) >>=
  return

I am not sure whether that is a good idea, since it would not allow me to use do notation should I want that. leftLift I can use with do:

bazDo = do
  x <- [1,2,3]
  y <- leftLift (+1) x
  z <- leftLift (*2) y
  return z

I didn't find a function on Hoogle with the signature of leftLift. Does such a function exist, and, if, what is it called? If not, what should I call it? And what would be the most idiomatic way of doing what I am trying to do?


Edit: Here's a version inspired by @dunlop's answer below:

infixl 4 <&>
(<&>) :: Functor f => f a -> (a -> b) -> f b
(<&>) = flip fmap

blah =
  [1,2,3] <&>
  (+1) <&>
  (*2) >>=
  return

I should also add that I was after a bind-variant, because I wanted to write my code in point-free style. For do-notation, I guess I don't need to "pretend" that I'm doing anything monadic, so I can use lets.

marc_r
  • 160
  • 7
  • 1
    I find myself using `=<<` more often than `>>=`, to stay consistent with composition and ordinary application. That nicely avoids the direction reversal in the first example. – Ben Mar 08 '17 at 20:57
  • That's nice; I wasn't aware of it. I can then write `return . (*2) . (+1) =<< [1,2,3]`. And, yes, as @user2297560 says below, readability is all quite subjective. – marc_r Mar 08 '17 at 21:44

2 Answers2

4

Every Monad is a Functor (and an Applicative too). Your (>>$) is (flipped) fmap.

GHCi> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
GHCi> :t (<$>) -- Infix synonym for 'fmap'
(<$>) -- Infix synonym for 'fmap'
  :: Functor f => (a -> b) -> f a -> f b
GHCi> fmap ((*2) . (+1)) [1,2,3]
[4,6,8]
GHCi> (*2) . (+1) <$> ([1,2,3] >>= \x -> [1..x])
[4,4,6,4,6,8]

(By the way, a common name for flipped fmap is (<&>). That is, for instance, what lens calls it.)


If you are using do-notation, there is little reason to use any variant of fmap explicitly for this kind of transformation. Just switch your <- monadic bindings for let-bindings:

bazDo = do
  x <- [1,2,3]
  let y = (+1) x
      z = (*2) y
  return z
bazDo = do
  x <- [1,2,3]
  let y = (+1) x
  return ((*2) z)
duplode
  • 33,731
  • 7
  • 79
  • 150
  • I could define my `(>>$)` in terms of `fmap` then. I wouldn't want to use `(<$>)` directly, since it spoils the sequential appearance. I would like my code to read as "take numbers out of `[1,2,3]`, add `1` to them, and multiply them with `2` (my actual code would be larger than that). – marc_r Mar 08 '17 at 15:16
  • 1
    @marc_r The easiest way of maintaining the sequential appearance in this kind of monadic code is using do-notation. – duplode Mar 08 '17 at 15:20
  • Thus my `leftLift` function. I'll edit my post to include the `do` version. – marc_r Mar 08 '17 at 15:33
  • @marc_r I have edited the answer to add a suggestion related to that. – duplode Mar 08 '17 at 15:48
  • `let`s are a possibility. I am unsure about which version is the nicest. I was also thinking that my `leftLift` could perhaps be of more general usefulness (although I don't know yet how). – marc_r Mar 08 '17 at 16:21
  • @marc_r Between your `leftLift` and your `(>>$)`, I would definitely pick `(>>$)`. In the relvant use case here (i.e. left-to-right pointfree monadic code), you only need `leftLift` because you have used `(>>=)` beforehand, and you can replace them both with `(>>$)`. In other situations in which it might be used, I would just write `return . f` rather than using a special-purpose combinator like `leftLift`. – duplode Mar 08 '17 at 16:56
1

For better readability...

That's going to be subjective as people disagree on what constitutes readable.

That being said, I agree that sometimes it's easier to understand data transformations when they are written left to right. I think your >>$ is overkill, though. The & operator in Data.Function does the job:

import Data.Function

foo = [1,2,3] & fmap (+1) & fmap (*2)

I like that this says exactly what to start with and exactly what to do at each step from left to right. And unlike >>$, you aren't forced to remain in the monad:

bar = [1,2,3] & fmap (+1) & fmap (*2) & sum & negate

Or you can just assemble your transformation beforehand and map it over your monad:

import Control.Category

f = (+1) >>> (*2)
quuz = fmap f [1,2,3]
user2297560
  • 2,953
  • 1
  • 14
  • 11
  • Good to know about `(&)`. In my particular application (beyond the toy example in my post), I am using the State monad, and I'm actually happy to remain in it. – marc_r Mar 08 '17 at 21:00