haskellmonadsidiomslifting

Using a pure function in a Haskell monad / left-lifting?


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.


Solution

  • 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)