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

*爱你&永不变心* 提交于 2019-12-11 06:28:02

问题


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.


回答1:


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)



回答2:


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]


来源:https://stackoverflow.com/questions/42674517/using-a-pure-function-in-a-haskell-monad-left-lifting

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!