Search code examples
haskellreduction

Eta reduction in haskell


I tried for a long time to reduct this function in haskell, I want to express for example:

mySum x y = x + y
mySum x y = (+) x y
mySum x = (+) x
mySum = (+) -- it's Messi's goal! 

My function it a little more complex, but I really can't do it, I was looking out here and there, and I know there are some techniques, like modify the right side, and use flip. I tried and I got stuck here:

zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f x y  = map  (uncurry f) (zip x y) 

Steps:

zipWith' f x y  = map  (uncurry f) (zip x y) 
zipWith' f x y  = flip  map  (zip x y) (uncurry f)
zipWith' f x y  = flip  map  (zip x y) $ uncurry f

and then I don't know how to continue...

I'm looking for an answer that could explain step by step how to achieve the "Messi's goal", I know is a lot to ask, so I will add as soon as I can a bounty to thank the effort


Solution

  • zipWith' f x y = map (uncurry f) (zip x y)
    

    Rewrite application to composition and eta-reduce:

    -- \y -> let g = map (uncurry f); h = zip x in (g . h) y
    -- let g = map (uncurry f); h = zip x in g . h
    
    zipWith' f x = map (uncurry f) . zip x
    

    Rewrite infix to prefix:

    -- g . h = (.) g h
    
    zipWith' f x = (.) (map (uncurry f)) (zip x)
    

    Rewrite application to composition and eta-reduce:

    -- \x -> let g = (.) (map (uncurry f)); h = zip in (g . h) x
    -- let g = (.) (map (uncurry f)); h = zip in g . h
    
    zipWith' f = (.) (map (uncurry f)) . zip
    

    Rewrite infix to prefix:

    -- g . h = (.) g h
    
    zipWith' f = (.) ((.) (map (uncurry f))) zip
    

    Use flip to move f to the right-hand side:

    -- flip f x y = f y x
    
    zipWith' f = flip (.) zip ((.) (map (uncurry f)))
    

    Rewrite application to composition:

    -- g (h (i x)) = (g . h . i) x
    
    zipWith' f = flip (.) zip (((.) . map . uncurry) f)
    

    Rewrite application to composition and eta-reduce:

    -- \f -> let g = flip (.) zip; h = (.) . map . uncurry in (g . h) f
    -- let g = flip (.) zip; h = (.) . map . uncurry in g . h
    
    zipWith' = (flip (.) zip) . ((.) . map . uncurry)
    

    Remove redundant parentheses:

    zipWith' = flip (.) zip . (.) . map . uncurry
    

    And simplify to infix if you like:

    zipWith' = (. zip) . (.) . map . uncurry
    

    This result isn’t very readable, though.


    Often when writing fully point-free code, you want to take advantage of the -> applicative and arrow combinators from Control.Arrow. Rather than trying to write a function like \ f x y -> ..., you can start by grouping the arguments into tuples to make them easier to rearrange and pipe around. In this case I’ll use \ (f, (x, y)) -> ...

    \ (f, (x, y)) -> map (uncurry f) (zip x y)
    

    We can eliminate the unpacking of (x, y) by applying uncurry to zip:

    \ (f, (x, y)) -> map (uncurry f) (uncurry zip (x, y))
    \ (f, xy) -> map (uncurry f) (uncurry zip xy)
    

    Now we have a simple case: applying two functions (uncurry and uncurry zip) to two arguments (f and xy), then combining the results (with map). For this we can use the *** combinator from Control.Arrow, of type:

    (***) :: Arrow a => a b c -> a b' c' -> a (b, b') (c, c')
    

    Specialised to functions, that’s:

    (***) @(->) :: (b -> c) -> (b' -> c') -> (b, b') -> (c, c')
    

    This just lets us apply a function to each element of a pair. Perfect!

    uncurry *** uncurry zip
      :: (a -> b -> c, ([x], [y])) -> ((a, b) -> c, [(x, y)])
    

    You can think of uncurry f as combining the elements of a pair using the function f. So here we can combine the results using uncurry map:

    uncurry map . (uncurry *** uncurry zip)
      :: (a -> b -> c, ([a], [b])) -> [c]
    

    And you can think of curry as turning a function on tuples into a multi-argument function. Here we have two levels of tuples, the outer (f, xy) and the inner (x, y). We can unpack the outer one with curry:

    curry $ uncurry map . (uncurry *** uncurry zip)
      :: (a -> b -> c) -> ([a], [b]) -> [c]
    

    Now, you can think of fmap f in the -> applicative as “skipping over” the first argument:

    fmap @((->) _) :: (a -> b) -> (t -> a) -> t -> b
    

    So we can unpack the second tuple using fmap curry:

    fmap curry $ curry $ uncurry map . (uncurry *** uncurry zip)
      :: (a -> b -> c) -> [a] -> [b] -> [c]
    

    And we’re done! Or not quite. When writing point-free code, it pays to break things out into many small reusable functions with clearer names, for example:

    zipWith' = untuple2 $ combineWith map apply zipped
      where
        untuple2 = fmap curry . curry
        combineWith f g h = uncurry f . (g *** h)
        apply = uncurry
        zipped = uncurry zip
    

    However, while knowing these techniques is useful, all this is just unproductive trickery that’s easy to get lost in. Most of the time, you should only use point-free style in Haskell when it’s a clear win for readability, and neither of these results is clearer than the simple original version:

    zipWith' f x y = map (uncurry f) (zip x y)
    

    Or a partially point-free version:

    zipWith' f = map (uncurry f) .: zip
      where (.:) = (.) . (.)