haskellfunctional-programmingfunctorcurryingpartial-application

About mapping on the eventual result of a multi-argument function


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.


Solution

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