Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

haskell fmap . fmap function, function over two functors

Tags:

haskell

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?

like image 429
Emmanuel Touzery Avatar asked Nov 30 '22 10:11

Emmanuel Touzery


2 Answers

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

like image 196
Reid Barton Avatar answered Dec 19 '22 10:12

Reid Barton


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.

like image 33
leftaroundabout Avatar answered Dec 19 '22 10:12

leftaroundabout