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
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
is actually not too bad, is it? Often I'd just stay with that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With