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.