Suppose for a minute that we think the following is a good idea:
data Fold x y = Fold {start :: y, step :: x -> y -> y}
fold :: Fold x y -> [x] -> y
Under this scheme, functions such as length or sum can be implemented by calling fold with the appropriate Fold object as argument.
Now, suppose you want to do clever optimisation tricks. In particular, suppose you want to write
unFold :: ([x] -> y) -> Fold x y
It should be relatively easy to rule a RULES pragma such that fold . unFold = id. But the interesting question is… can we actually implement unFold?
Obviously you can use RULES to apply arbitrary code transformations, whether or not they preserve the original meaning of the code. But can you really write an unFold implementation which actually does what its type signature suggests?
You can, but you need to make a slight modification to
Foldin order to pull it off.All functions on lists can be expressed as a fold, but sometimes to accomplish this, extra bookkeeping is needed. Suppose we add an additional type parameter to your
Foldtype, which passes along this additional contextual information.Now we can implement
foldlike soNow what happens when we try to go the other way?
Where does the
ccome from? Functions are opaque values, so it’s hard to know how to inspect a function and know which contextual information it relies on. So, let’s cheat a little. We’re going to have the “contextual information” be the entire list, so then when we get to the leftmost element, we can just apply the function to the original list, ignoring the prior cumulative results.Now, sadly, this does not necessarily compose with
fold, because it requires thatcmust be[a]. Let’s fix that by hidingcwith existential quantification.Now, it should always be true that
fold . unFold = id. And, given a relaxed notion of equality for theFolddata type, you could also say thatunFold . fold = id. You can even provide a smart constructor that acts like the oldFoldconstructor: