brodal queue

zipRev

Haskell; functional programming (9.25.24)

A question- is it possible to reverse a functional list, then zip it with another list, in one pass? In Haskell, a solution to this problem looks something like this:

zipRev = foldl f b
    where   b = const []
            f z x = \case
                        [] -> []
                        (y:ys) -> (x,y) : z ys
            

Note that this requires the LambdaCase extension.

 

The function is a pretty elegant application of foldl, but I don't think its implementation is intuitively obvious - which begs the question of how one might come up with it from scratch.

 

Defining zip in terms of foldr and some folding function g is a potentially fruitful start, since foldl (flip g) can be loosely thought of as folding the first input list in "reverse".

 

zip = foldr g b implies that zip xs = foldr g b xs.

 

Thus, zip [] = b by the definition of foldr, meaning that b = \ys -> [] or const [] based on the semantics of zip.

 

At the same time, zip (x:xs) = g x (foldr g b xs), meaning that zip (x:xs) ys = g x (foldr g b xs) ys.

 

This tells us that our folding function g takes the head of xs, the result of folding its tail, and ys as arguments and returns a zipped list. Remembering that folding its tail yields a function that zips its tail with whatever list you apply it to, we can straightforwardly define g as:

g x z = \case
            [] -> []
            (y:ys) -> (x,y) : z ys
            

Now, foldl (flip g) b builds up a thunk such that the outermost application of flip g is partially applied to the last element of xs, before being applied to ys. This is the behavior we want - the elements of xs are being paired up with the elements of ys in reverse order!

 

With this in mind, we can define f = flip g and zipRev = foldl f b.