I'm reading through Some Tricks for List Manipulation, and it contains the following:
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k c = k (\((y:ys),r) -> c (ys,(x,y):r))
What we can see here is that we have two continuations stacked on top
of each other. When this happens, they can often “cancel out”, like
so:
zipRev xs ys = snd (foldr f (ys,[]) xs)
where
f x (y:ys,r) = (ys,(x,y):r)
I don't understand how you "cancel out" stacked continuations to get from the top code block to the bottom one. What pattern do you look for to make this transformation, and why does it work?
A function f :: a -> b can be "disguised" inside double continuations as a function f' :: ((a -> r1) -> r2) -> ((b -> r1) -> r2).
obfuscate :: (a -> b) -> ((a -> r1) -> r2) -> (b -> r1) -> r2
obfuscate f k2 k1 = k2 (k1 . f)
obfuscate has the nice property that it preserves function composition and identity: you can prove that obfuscate f . obfuscate g === obfuscate (f . g) and that obfuscate id === id in a few steps. That means that you can frequently use this transformation to untangle double-continuation computations that compose obfuscated functions together by factoring the obfuscate out of the composition. This question is an example of such an untangling.
The f in the top code block is the obfuscated version of the f in the bottom block (more precisely, top f x is the obfuscated version of bottom f x). You can see this by noticing how top f applies the outer continuation to a function that transforms its input and then applies the whole thing to the inner continuation, just like in the body of obfuscate.
So we can start to untangle zipRev:
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x = obfuscate (\(y:ys,r) -> (ys,(x,y):r))
Since the action of foldr here is to compose a bunch of obfuscated functions with each other (and apply it all to id, which we can leave on the right), we can factor the obfuscate to the outside of the whole fold:
zipRev xs ys = obfuscate (\accum -> foldr f accum xs) id snd (ys,[])
where
f x (y:ys,r) = (ys,(x,y):r)
Now apply the definition of obfuscate and simplify:
zipRev xs ys = obfuscate (\accum -> foldr f accum xs) id snd (ys,[])
zipRev xs ys = id (snd . (\accum -> foldr f accum xs)) (ys,[])
zipRev xs ys = snd (foldr f (ys,[]) xs)
QED!
Given a function
g :: a₁ -> a₂
we can lift it to a function on continuations, switching the order:
lift g = (\c a₁ -> c (g a₁))
:: (a₂ -> t) -> a₁ -> t
This transformation is a contravariant functor, which is to say that it interacts with function composition by switching its order:
g₁ :: a₁ -> a₂
g₂ :: a₂ -> a₃
lift g₁ . lift g₂
== (\c₁ a₁ -> c₁ (g₁ a₁)) . (\c₂ a₂ -> c₂ (g₂ a₂))
== \c₂ a₁ -> (\a₂ -> c₂ (g₂ a₂)) (g₁ a₁)
== \c₂ a₁ -> c₂ (g₂ (g₁ a₁))
== lift (g₂ . g₁)
:: (a₃ -> t) -> a₁ -> t
lift id
== (\c a₁ -> c a₁)
== id
:: (a₁ -> t) -> a₁ -> t
We can lift the lifted function again in the same way to a function on stacked continuations, with the order switched back:
lift (lift g)
== (\k c -> k ((\c a₁ -> c (g a₁)) c))
== (\k c -> k (\a₁ -> c (g a₁)))
:: ((a₁ -> t) -> u) -> (a₂ -> t) -> u
Stacking two contravariant functors gives us a (covariant) functor:
lift (lift g₁) . lift (lift g₂)
== lift (lift g₂ . lift g₁)
== lift (lift (g₁ . g₂))
:: ((a₁ -> t) -> u) -> (a₃ -> t) -> u
lift (lift id)
== lift id
== id
:: ((a₁ -> t) -> u) -> (a₁ -> t) -> u
This is exactly the transformation being reversed in your example, with g = \(y:ys, r) -> (ys, (x, y):r). This g is an endomorphism (a₁ = a₂), and the foldr is composing together a bunch of copies of it with various x. What we’re doing is replacing the composition of double-lifted functions with the double-lift of the composition of the functions, which is just an inductive application of the functor laws:
f :: x -> a₁ -> a₁
c :: (a₁ -> t) -> u
xs :: [x]
foldr (\x -> lift (lift (f x))) c xs
== lift (lift (\a₁ -> foldr f a₁ xs)) c
:: (a₁ -> t) -> u
Let's try to understand this code from an elementary point of view. What does it even do, one wonders?
zipRev xs ys = foldr f id xs snd (ys,[])
where
-- f x k c = k (\(y:ys, r) -> c (ys, (x,y):r))
f x k c = k (g x c)
-- = (k . g x) c -- so,
-- f x k = k . g x
g x c (y:ys, r) = c (ys, (x,y):r)
Here we used lambda lifting to recover the g combinator.
So then because f x k = k . g x were k goes to the left of x, the input list is translated into a reversed chain of compositions,
foldr f id [x1, x2, x3, ..., xn] where f x k = k . g x
===>>
(((...(id . g xn) . ... . g x3) . g x2) . g x1)
and thus, it just does what a left fold would do,
zipRev [] ys = []
zipRev [x1, x2, x3, ..., xn] ys
= (id . g xn . ... . g x3 . g x2 . g x1) snd (ys, [])
= g xn (g xn1 ( ... ( g x3 ( g x2 ( g x1 snd)))...)) (ys, [])
where ----c--------------------------------------------
g x c (y:ys, r) = c (ys, (x,y):r)
So we went to the deep end of the xs list, and then we come back consuming the ys list left-to-right (i.e. top-down) on our way back right-to-left on the xs list (i.e. bottom-up). This is straightforwardly coded as a right fold with strict reducer, so the flow is indeed right-to-left on the xs. The bottom-most action (snd) in the chain is done last, so in the new code it becomes the topmost (still done last):
zipRev xs ys = snd (foldr h (ys,[]) xs)
where
h x (y:ys, r) = (ys, (x,y):r)
g x c was used as a continuation in the original code, with c as a second-tier continuation; but it's actually all just been a regular fold from the right, all along.
So indeed it zips the reversed first list with the second. It's also unsafe; it misses a clause:
g x c ([], r) = c ([], r) -- or just `r`
g x c (y:ys, r) = c (ys, (x,y):r)
(update:) The answers by duplode (and Joseph Sible) do the lambda lifting a bit differently, in a way which is better suited to the task. It goes like this:
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k c = k (\((y:ys), r) -> c (ys, (x,y):r))
= k (c . (\((y:ys), r) -> (ys, (x,y):r)) )
= k (c . g x)
g x = (\((y:ys), r) -> (ys, (x,y):r))
{- f x k c = k ((. g x) c) = (k . (. g x)) c = (. (. g x)) k c
f x = (. (. g x)) -}
so then
foldr f id [ x1, x2, ... , xn ] snd (ys,[]) =
= ( (. (. g x1)) $ (. (. g x2)) $ ... $ (. (. g xn)) id ) snd (ys,[]) -- 1,2...n
= ( id . (. g xn) . ... . (. g x2) . (. g x1) ) snd (ys,[]) -- n...2,1
= ( snd . g x1 . g x2 . ... . g xn ) (ys,[]) -- 1,2...n!
= snd $ g x1 $ g x2 $ ... $ g xn (ys,[])
= snd $ foldr g (ys,[]) [x1, x2, ..., xn ]
Simple. :) Flipping twice is no flipping at all.
Let's begin with a few cosmetic adjustments:
-- Note that `g x` is an endomorphism.
g :: a -> ([b], [(a,b)]) -> ([b], [(a,b)])
g x ((y:ys),r) = (ys,(x,y):r)
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k = \c -> k (c . g x)
f feeds a continuation (c . g x) to another function (k, a "double continuation", as user11228628 puts it).
While we might reasonably expect that repeated usage of f as the fold proceeds will somehow compose the g x endomorphisms made out of the elements of the list, the order in which the endomorphisms are composed might not be immediately obvious, so we'd better walk through a few fold steps to be sure:
-- x0 is the first element, x1 the second, etc.
f x0 k0
\c -> k0 (c . g x0)
\c -> (f x1 k1) (c . g x0) -- k0 is the result of a fold step.
\c -> (\d -> k1 (d . g x1)) (c . g x0) -- Renaming a variable for clarity.
\c -> k1 (c . g x0 . g x1)
-- etc .
-- xa is the *last* element, xb the next-to-last, etc.
-- ka is the initial value passed to foldr.
\c -> (f xa ka) (c . g x0 . g x1 . . . g xb)
\c -> (\d -> ka (d . g xa)) (c . g x0 . g x1 . . . g xb)
\c -> ka (c . g x0 . g x1 . . . g xb . g xa)
ka, the initial value passed to foldr, is id, which makes things quite a bit simpler:
foldr f id xs = \c -> c . g x0 . g x1 . . . g xa
Since all we do with the c argument passed to foldr f id xs is post-composing it with the endomorphisms, we might as well factor it out of the fold:
zipRev xs ys = (snd . foldr h id xs) (ys,[])
where
h x e = g x . e
Note how we have gone from c . g x to g x . e. That can arguably be described as a collateral effect of the CPS trickery in the original implementation.
The final step is noticing how h x e = g x . e corresponds exactly to what we would do to implement foldr in terms of foldMap for the Endo monoid. Or, to put it more explicitly:
foldEndo g i xs = foldr g i xs -- The goal is obtaining an Endo-like definition.
foldEndo _ i [] = i
foldEndo g i (x : xs) = g x (foldEndo g i xs)
foldEndo g i xs = go xs i
where
go [] = \j -> j
go (x : xs) = \j -> g x (foldEndo g j xs)
foldEndo g i xs = go xs i
where
go [] = \j -> j
go (x : xs) = \j -> g x (go xs j)
foldEndo g i xs = go xs i
where
go [] = id
go (x : xs) = g x . go xs
foldEndo g i xs = go xs i
where
h x e = g x . e
go [] = id
go (x : xs) = h x (go xs)
foldEndo g i xs = go xs i
where
h x e = g x . e
go xs = foldr h id xs
foldEndo g i xs = foldr h id xs i
where
h x e = g x . e
That finally leads us to what we were looking for:
zipRev xs ys = snd (foldr g (ys,[]) xs)
user11228628's answer led me to understanding. Here's a few insights I had while reading it, and some step-by-step transformations.
Insights
The continuations don't directly cancel out. They can only eventually be canceled (by beta-reducing) because it's possible to factor them out.
The pattern you're looking for to do this transformation is \k c -> k (c . f) (or if you love unreadable pointfree, (. (. f))) for any f (note that the f isn't a parameter to the lambda).
As duplode points out in a comment, continuation-passing style functions can be considered a functor, and obfuscate is their definition of fmap.
The trick of pulling a function like this out of foldr works for any function that could be a valid fmap.
Full transformation from the first code block to the second
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k c = k (\((y:ys),r) -> c (ys,(x,y):r))
Pull c out of the lambda
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x k c = k (c . \((y:ys),r) -> (ys,(x,y):r))
Substitute obfuscate for its definition
zipRev xs ys = foldr f id xs snd (ys,[])
where
f x = obfuscate (\((y:ys),r) -> (ys,(x,y):r))
Pull obfuscate out of the lambda
zipRev xs ys = foldr f id xs snd (ys,[])
where
f = obfuscate . \x ((y:ys),r) -> (ys,(x,y):r)
Pull obfuscate out of f
zipRev xs ys = foldr (obfuscate . f) id xs snd (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
Since obfuscate follows the Functor laws, we can pull it out of foldr
zipRev xs ys = obfuscate (flip (foldr f) xs) id snd (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
Inline obfuscate
zipRev xs ys = (\k c -> k (c . flip (foldr f) xs)) id snd (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
Beta-reduce
zipRev xs ys = (id (snd . flip (foldr f) xs)) (ys,[])
where
f x ((y:ys),r) = (ys,(x,y):r)
Simplify
zipRev xs ys = snd (foldr f (ys,[]) xs)
where
f x (y:ys,r) = (ys,(x,y):r)
Justification for pulling functions that are valid fmaps out of foldr
foldr (fmap . f) z [x1,x2,...,xn]
Expand the foldr
(fmap . f) x1 . (fmap . f) x2 . ... . (fmap . f) xn $ z
Inline the inner .s
fmap (f x1) . fmap (f x2) . ... . fmap (f xn) $ z
Apply the Functor laws
fmap (f x1 . f x2 . ... . f xn) $ z
Eta-expand the section in parentheses
fmap (\z2 -> f x1 . f x2 . ... . f xn $ z2) z
Write the lambda body in terms of foldr
fmap (\z2 -> foldr f z2 [x1,x2,...,xn]) z
Write the lambda body in terms of flip
fmap (flip (foldr f) [x1,x2,...,xn]) z
Bonus: Justification for pulling functions that are valid contramaps out of foldr
foldr (contramap . f) z [x1,x2,...,xn]
Expand the foldr
(contramap . f) x1 . (contramap . f) x2 . ... . (contramap . f) xn $ z
Inline the inner .s
contramap (f x1) . contramap (f x2) . ... . contramap (f xn) $ z
Apply the Contravariant laws
contramap (f xn . ... . f x2 . f x1) $ z
Eta-expand the section in parentheses
contramap (\z2 -> f xn . ... . f x2 . f x1 $ z2) z
Write the lambda body in terms of foldr
contramap (\z2 -> foldr f z2 [xn,...,x2,x1]) z
Write the lambda body in terms of flip
contramap (flip (foldr f) [xn,...,x2,x1]) z
Apply foldr f z (reverse xs) = foldl (flip f) z xs
contramap (flip (foldl (flip f)) [x1,x2,...,xn]) z
Related
interface Foldable t where
foldr : (func : elem -> acc -> acc) -> (init : acc) -> (input : t elem) -> acc
foldl : (func : acc -> elem -> acc) -> (init : acc) -> (input : t elem) -> acc
foldl f z t = foldr (flip (.) . flip f) id t z
What does foldr (flip (.) . flip f) id t z mean here?
And is there another way to implement foldl using foldr?
Thanks for answering.
The first argument in the foldl defined above could be turned into a lambda like this:
\element -> (. (flip f element))
-- or
\element prevFun -> prevFun . flip f element
-- or
\element prevFun next -> prevFun $ f next element
Let's take an example where you want to fold left over a list [a, b, c]. You want your end result to be f (f (f z a) b) c), where z is the accumulator.
In the first iteration, the first argument would be c and the second id. Thus, the result would be id . flip f c, or just flip f c (since id . f is simply f, as #chepner pointed out).
The second iteration would result in flip f c . flip f b, and after that you would get flip f c . flip f b . flip f a (basically, adding a . flip f x for every next element x.
This expands to:
acc -> flip f c (flip f b (flip f a acc))
-- or
acc -> flip f c (flip f b (f acc a))
-- or
acc -> f (f (f acc a) b) c)
Et voila! We have a function that takes an accumulator acc, and returns the result of foldl f acc t, so we simply apply that to z, the actual accumulator.
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
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 (.:) = (.) . (.)
Say I have functions
g :: a -> b, h :: a -> c
and
f :: b -> c -> d.
Is it possible to write the function
f' :: a -> a -> d
given by
f' x y = f (g x) (h y)
in point free style?.
One can write the function
f' a -> d, f' x = f (g x) (h x)
in point free style by setting
f' = (f <$> g) <*> h
but I couldn't figure out how to do the more general case.
We have:
k x y = (f (g x)) (h y)
and we wish to write k in point-free style.
The first argument passed to k is x. What do we need to do with x? Well, first we need to call g on it, and then f, and then do something fancy to apply this to (h y).
k = fancy . f . g
What is this fancy? Well:
k x y = (fancy . f . g) x y
= fancy (f (g x)) y
= f (g x) (h y)
So we desire fancy z y = z (h y). Eta-reducing, we get fancy z = z . h, or fancy = (. h).
k = (. h) . f . g
A more natural way to think about it might be
┌───┐ ┌───┐
x ───│ g │─── g x ───│ │
/ └───┘ │ │
(x, y) │ f │─── f (g x) (h y)
\ ┌───┐ │ │
y ───│ h │─── h y ───│ │
└───┘ └───┘
└──────────────────────────────┘
k
Enter Control.Arrow:
k = curry ((g *** h) >>> uncurry f)
Take a look at online converter
It converted
f' x y = f (g x) (h y)
into
f' = (. h) . f . g
with the flow of transformations
f' = id (fix (const (flip ((.) . f . g) h)))
f' = fix (const (flip ((.) . f . g) h))
f' = fix (const ((. h) . f . g))
f' = (. h) . f . g
This is slightly longer, but a little easier to follow, than (. h) . f. g.
First, rewrite f' slightly to take a tuple instead of two arguments. (In otherwords, we're uncurrying your original f'.)
f' (x, y) = f (g x) (h y)
You can pull a tuple apart with fst and snd instead of pattern matching on it:
f' t = f (g (fst t)) (h (snd t))
Using function composition, the above becomes
f' t = f ((g . fst) t) ((h . snd) t)
which, hey, looks a lot like the version you could make point-free using applicative style:
f' = let g' = g . fst
h' = h . snd
in (f <$> g') <*> h'
The only problem left is that f' :: (a, a) -> d. You can fix this by explicitly currying it:
f' :: a -> a -> d
f' = let g' = g . fst
h' = h . snd
in curry $ (f <$> g') <*> h'
(This is very similar, by the way, to the Control.Arrow solution added by Lynn.)
Using the "three rules of operator sections" as applied to the (.) function composition operator,
(.) f g = (f . g) = (f .) g = (. g) f -- the argument goes into the free slot
-- 1 2 3
this is derivable by a few straightforward mechanical steps:
k x y = (f (g x)) (h y) -- a (b c) === (a . b) c
= (f (g x) . h) y
= (. h) (f (g x)) y
= (. h) ((f . g) x) y
= ((. h) . (f . g)) x y
Lastly, (.) is associative, so the inner parens may be dropped.
The general procedure is to strive to reach the situation where eta-reduction can be performed, i.e. we can get rid of the arguments if they are in same order and are outside any parentheses:
k x y = (......) y
=>
k x = (......)
Lather, rinse, repeat.
Another trick is to turn two arguments into one, or vice versa, with the equation
curry f x y = f (x,y)
so, your
f (g x) (h y) = (f.g) x (h y) -- by B-combinator rule
= (f.g.fst) (x,y) ((h.snd) (x,y))
= (f.g.fst <*> h.snd) (x,y) -- by S-combinator rule
= curry (f.g.fst <*> h.snd) x y
This is the same as the answer by #chepner, but presented more concisely.
So, you see, your (f.g <*> h) x1 just becomes (f.g.fst <*> h.snd) (x,y). Same difference.
1(because, for functions, (<$>) = (.))
Control.Compose
(g ~> h ~> id) f
Data.Function.Meld
f $* g $$ h *$ id
Data.Function.Tacit
lurryA #N2 (f <$> (g <$> _1) <*> (h <$> _2))
lurryA #N5 (_1 <*> (_2 <*> _4) <*> (_3 <*> _5)) f g h
Related articles
Semantic Editor Combinators, Conal Elliott, 2008/11/24
Pointless fun, Matt Hellige, 2008/12/03
I already asked a different question realted to this proof, so I'm sorry for duplicate. Just nobody would answer any more, because the other one has an answer.
I am trying to prove the following statement by structural induction:
foldr f st (xs++yx) = f (foldr f st xs) (foldr f st ys) (foldr.3)
These are the definitions of foldr:
foldr f st [] = st (foldr.1)
foldr f st x:xs = f x (foldr f st xs) (foldr.2)
Now I want to start working on the base case passing the empty list to xs. I have this, but I don't know if it is correct.
foldr f st ([]++ys) = f (foldr f st []) (foldr f st ys)
LHS:
foldr f st ([]++ys)
= foldr f st ys by (++) and by (foldr.1)
RHS:
f (foldr f st []) (foldr f st ys) =
= f st (foldr f st ys) by (foldr.1)
= foldr f st ys by def of st = 0 and f = (+)
LHS = RHS, therefore base case holds
Now this is what I have for my inductive step:
Assume that:
foldr f st (xs ++ ys) = f (foldr f st xs) (foldr f st ys) (ind. hyp)
Show that:
foldr f st (x:xs ++ ys) = f (foldr f st x:xs) (foldr f st ys)
LHS:
foldr f st (x:xs ++ ys)
= f x (foldr f st (xs++yx)) (by foldr.2)
= f x (f (foldr f st xs) (foldr f st ys) ) (by ind. hyp)
= f (f x (foldr f st xs)) (foldr f st ys) (by assosiativity of f)
RHS:
f (foldr f st x:xs) (foldr f st ys)
= f (f x (foldr f st xs)) (foldr f st ys) (by foldr.2)
LHS = RHS, therefore inductive step holds. End of proof.
I am not sure if this proof is valid. I need some help in determining if it correct and if not - what part of it is not.
before you start to prove something, check a few examples, e.g.,
xs = ys = [] ; st = True ; f _ _ = False
Upd: The question was edited multiple times. The following makes sense in revision 1 of the question.
This is not possible to prove. You need f to be a monoid: f a (f b c) == f (f a b) c with f a st == a and f st a == a.
A provable statement would be foldr f st (xs++ys) == foldr f (foldr f st ys) xs
Assuming f with st define a monoid, we get the following statements:
foldr f st ([]++ys) ==
-- by definition of neutral element of list monoid, []++ys == ys
== foldr f st ys
-- by definition of neutral element of `f` monoid, f st a == a
== f st (foldr f st ys)
-- by definition of foldr, (foldr f st []) == st
== f (foldr f st []) (foldr f st ys)
Then,
foldr f st ((x:xs)++ys) ==
-- by associativity of list monoid, (x:xs)++ys == x:(xs++ys)
== foldr f st (x:(xs++ys))
-- by definition of foldr, foldr f st (x:t) == f x (foldr f st t)
== f x (foldr f st (xs++ys))
-- by induction, foldr f st (xs++ys) == f (foldr f st xs) (foldr f st ys)
== f x (f (foldr f st xs) (foldr f st ys))
-- by associativity of monoid `f`, f x (f y z) == f (f x y) z
== f (f x (foldr f st xs)) (foldr f st ys)
-- by definition of foldr, f x (foldr f st t) == foldr f st (x:t)
== f (foldr f st (x:xs)) (foldr f st ys)
In essence, this is the proof of the monoid homomorphism. Lists being a free monoid, the homomorphism exists, and is exactly of the form you are seeking - it is the catamorphism for f.
For foldr we have the fusion law: if f is strict, f a = b, and
f (g x y) = h x (f y) for all x, y, then f . foldr g a = foldr h b.
How can one discover/derive a similar law for foldr1? (It clearly can't even take the same form - consider the case when both sides act on [x].)
You can use free theorems to derive statements like the fusion law. The Automatic generation of free theorems does this work for you, it automatically derives the following statement if you enter foldr1 or the type (a -> a -> a) -> [a] -> a.
If f strict and f (p x y) = q (f x) (f y)) for all x and y you have f (foldr1 p z) = foldr1 q (map f z)). That is, in contrast to you statement about foldr you get an additional map f on the right hand side.
Also note that the free theorem for foldr is slightly more general than your fusion law and, therefore, looks quite similar to the law for foldr1. Namely you have for strict functions g and f if g (p x y) = q (f x) (g y)) for all x and y then g (foldr p z v) = foldr q (g z) (map f v)).
I don't know if there's going to be anything satisfying for foldr1. [I think] It's just defined as
foldr1 f (x:xs) = foldr f x xs
let's first expand what you have above to work on the entire list,
f (foldr g x xs) = foldr h (f x) xs
for foldr1, you could say,
f (foldr1 g xs) = f (foldr g x xs)
= foldr h (f x) xs
to recondense into foldr1, you can create some imaginary function that maps f to the left element, for a result of,
f . foldr1 g = foldr1 h (mapfst f) where
mapfst (x:xs) = f x : xs