3

I known that r -> a is a Functor in a, and that fmap = (.) for it.

This means that when I do fmap f g, with g :: r -> a, f is applied to the result of g as soon as the latter is fed with a value of type r. However, if a is a function type, e.g. a unary function b -> c, then there's a difference between applying f to that function (which is what happens in reality) and applying f to the eventual result of g (which could be desirable in some cases; couldn't it?).

How do I map on the eventual result of g? In this case of g :: r -> b -> c it seems easy, I can just uncurry $ fmap f $ curry g. But what if also c is a function type?

Or, in other words, how do I map on the final result of multi-variable function? Is the curry/uncurry trick necessary/doable in general?

(Related question here.)

As it's apparent from comments/answers, I have not realized I was essentially asking the same question I've already asked some days ago. Probably it was not apparent to me because of how I got here. Essentially, what led me to ask this question is another one storming in my head:

If I can use liftA2 (&&), liftA2 (||), and similar to combine unary predicates, how do I combine binary predicates? To answer to this, I played around a bit in GHCi, and came up with this

liftIntoBinaryFunc p = \q r -> curry $ (liftA2 p) (uncurry q) (uncurry r)

which would allow doing something like this:

-- some binary predicate
distanceLE3 x y = 3 >= abs (x - y)
sameSign x y = ((==) `on` signum) x y

-- function to lift into binary functions
liftIntoBinaryFunc p = \q r -> curry $ (liftA2 p) (uncurry q) (uncurry r)

-- lifting && into binary functions to Bool
and' = liftIntoBinaryFunc (&&)

-- combining 
pred = distance3 `and'` sameSign

-- using
p 3 18   -- False
p 3 1    -- True
p 3 (-1) -- False

However, this question too generalizes to how do I combine predicates of arbitrary (though common) arity?, and probably the answer is the same.

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 1
    "*But what if also `c` is a function type?*" - then it's another level more, and a different `fmap` function. There is no way to have a generic function that maps over the "eventual result" of a function with arbitrarily many parameters. – Bergi Apr 05 '21 at 17:43
  • 1
    for binary functions it's `(fmap . fmap)`. and if it's arbitrary number, you've already asked about [that](https://stackoverflow.com/questions/66883973/about-mapping-thorught-several-nested-functorial-levels) recently. – Will Ness Apr 05 '21 at 17:46
  • Wow. @WillNess, I didn't realize it was exactly the same problem :D Probably because I got to this formulation of the question from a totally different angle. Essentially what led me to ask this question is another one storming in my head: _If I can `liftA2` `&&`, `||` and others to combine unary predicates, how do I combine binary predicates?_ Probably this would be a 3rd shape to the same actual question, wouldn't it? I will add this in the question, which will probably make me look less stupid too. – Enlico Apr 05 '21 at 17:57
  • The answer is the same: apply `liftA2` multiple times. – Daniel Wagner Apr 05 '21 at 18:03
  • `(liftA2 . liftA2) (&&) ((>=).(+10)) (<=) 9 10`. – Will Ness Apr 05 '21 at 18:05
  • Yeah, I got there myself, then I thought about combining n-ary predicates, finally noticed it all boiled down to mapping to the final result of a function, and so I asked the present question, failing in understanding it was my own older question. I'm just a bit dumb sometimes. – Enlico Apr 05 '21 at 18:11
  • @Enlico Try to manually unify `(fmap . fmap)` (or better `(.) . (.)` for the sake of simplicity) to get a better understanding how this works. –  Apr 05 '21 at 20:37
  • (Ahah, the boobs operator, right?) @scriptum, I still have to understand what "unify" means. – Enlico Apr 05 '21 at 20:39

2 Answers2

2

If you'd like to accept n arguments and then apply f, you may call fmap n times. So, if g :: r -> b -> c, then

(fmap . fmap) f g

will apply f to the value of type c that results from applying g to two arguments, while if h :: a -> b -> c -> d -> e -> f -> g, then

   a      b      c      d      e      f
(fmap . fmap . fmap . fmap . fmap . fmap) f h

will apply f to the value of type g that results from applying h to 6 arguments.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
2

r -> b -> c is r -> (b -> c) and so (fmap . fmap) (f :: c -> d) (g :: r -> b -> c) :: r -> b -> d:

> foo :: (c -> d) -> (r -> (b -> c)) -> r -> (b -> d) ;
  foo = fmap . fmap

foo :: (c -> d) -> (r -> b -> c) -> r -> b -> d

> bar :: (c -> d) -> (r -> (b -> (t -> c))) -> r -> (b -> (t -> d)) ;
  bar = fmap . fmap . fmap

bar :: (c -> d) -> (r -> b -> t -> c) -> r -> b -> t -> d

But you will have to know how many nested levels are there, to compose the fmaps accordingly, or use approaches from your other recent question.

Will Ness
  • 70,110
  • 9
  • 98
  • 181