简体   繁体   中英

haskell fmap . fmap function, function over two functors

I find myself more and more often doing something like that...

I have a function f :: IO [a] then I want to apply on it a function of type g :: a -> b , to get IO [b] .

The long way is:

x <- f
let y = fmap g x

Which I then shortened to:

x <- f
let y = g <$> x

and nowadays I rather do:

y <- fmap g <$> f

but then with the Functor laws, I can see that I can do even:

(<$$>) = fmap . fmap
y <- g <$$> f

and though I saw often mentions of this fmap . fmap fmap . fmap I see it has no special binding in the base library, while it seems like a massively frequent idiom for me. I literally could use this all over the place! So now I wonder, am I not trying hard enough to write pure code and that's why I hit this more than more experienced haskell programmers... Or is there a more elegant way to achieve this that explains why this idiom is not used by others much?

I am of the opinion (which seems to be rather unpopular among parts of the Haskell community) that you should write your code to express your intent clearly and follow the expectations of the reader, and not then algebraically refactor your code into an equivalent form which is "neater", but not any more clear.

In this case there doesn't seem to be any real meaning to the fact that you have two occurrences of fmap here. The fact that you are adjusting the result of an IO action is not logically conected to the fact that the adjustment happens to be mapping over a list. If you see the " fmap . fmap pattern" many times in your code, that's just because fmap is a quite common function.

If you write

    y <- g <$$> x      -- or any other made-up operator name

not only do you force your reader to learn an unfamiliar operator, but once they have learned it, they have to expand it to y <- fmap g <$> x anyways to understand what is happening, since you grouped two unrelated operations together.

The best form is your y <- fmap g <$> x (or even better, y <- map g <$> x ), since it fits into a familiar pattern var <- function <$> action .

(By the way, you mentioned the Functor laws, but they are in no way relevant to any of the rewritings in your question, which just amount to expanding definitions.)

Well, what you have there is a composition of functors . The most widespread implementation of that concept – albeit not really as general as you might like – are monad transformers .

http://hackage.haskell.org/package/transformers-0.4.3.0/docs/Control-Monad-Trans-List.html#v:ListT

f' :: ListT IO a
f' = ListT f

On that, you can simply use fmap g , to get a value h' :: ListT IO b that can with runListT then be evaluated as IO [b] .

Whether that wrapping and unwrapping is worthwhile depends on how many operations you do in a row, that can use the same composition. For some applications, monad transformers are just terrific; in other cases they just make stuff more clunky than it already is. fmap . fmap fmap . fmap is actually not too bad, is it? Often I'd just stay with that.

Functors (and applicatives) compose and the Compose newtype defines functor and applicative instances for the composition of two functors. This means you can wrap your IO [a] in a Compose IO [] a and apply fmap eg

import Data.Functor.Compose
getCompose $ fmap g $ Compose f

I'm finding myself doing that often as well and find it really confusing. I found liftM . map liftM . map easier to read than fmap . fmap fmap . fmap .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM