9

So, there is something known as a "universal property of fold", stating exactly following:

g [] = i; g (x:xs) = f x (g xs) <=> g = fold f i

However, as you probably now, there are rare cases like dropWhile, which can not be redefined as fold f i unless you generalize it.

The simplest yet obvious way to generalize is to redefine universal property:

g' y [] = j y; g' y (x:xs) = h y x xs (g' y xs) <=> g' y = fold (?) l


At this point I can make my assumption: I assume existence of somewhat function p :: a -> b -> b, which would satisfy the equation g' y = fold p l. Let's try to solve given equation with help of universal property, mention at the very beginning:

  • g' y [] = j y = fold p l [] = l => j y = l
  • g' y (x:xs) = h y x xs (g' y xs) = fold p l (x:xs) = p x (fold p l xs) = p x (g' y xs) => letting rs = (g' y xs), h y x xs rs = p x rs, which is wrong: xs occurs freely from the left and thus equality can't hold.

Now let me try to interpret result I've came up with and ask question. I see that the problem is xs emerging as unbound variable; it's true for various situations, including above mentioned dropWhile. Does it mean that the only way that equation can be solved is by "extending" rs to a pair of (rs, xs)? In other words, fold accumulates into tuple rather than a single type (ignoring the fact that tuple itself is a single type)? Is there any other way to generalize bypassing pairing?

Zazaeil
  • 3,900
  • 2
  • 14
  • 31
  • 1
    Actually you *can* implement `dropWhile` with the standard `fold`. – Willem Van Onsem Mar 06 '18 at 19:25
  • @WillemVanOnsem can you give an example? – Zazaeil Mar 06 '18 at 19:25
  • sorry, somehow I mixed `takeWhile` and `dropWhile` :) – Willem Van Onsem Mar 06 '18 at 19:27
  • @WillemVanOnsem, yeah. there is no way to implement `dropWhile`, it can be proven by simple calculation with help of universal property from the OP. in order to implement `dropWhile` one has to generalize to a pair `(rs, ys)`, than it's fine. the problem there is exactly the same: unbound `xs`. – Zazaeil Mar 06 '18 at 19:29
  • 2
    I am sorry, but could someone explain for a simple man, what `fold` is the talk about? Right or left? – Ignat Insarov Mar 06 '18 at 20:29
  • @IgnatInsarov It is a right fold -- the canonical way of tearing down a (cons) list. – duplode Mar 06 '18 at 20:33
  • @duplode I am then having trouble reconciling this question with the lengthy treatment of `foldr` in [The Monad.Reader Issue 6](https://wiki.haskell.org/wikiupload/1/14/TMR-Issue6.pdf). To my understanding of the article, there are several ways to build a `dropWhile` from a `foldr`. – Ignat Insarov Mar 06 '18 at 20:36
  • A relevant discussion (for both you and @IgnatInsarov , I guess): [*“Any function on finite lists that is defined by pairing the desired result with the argument list can always be redefined in terms of fold”*](https://stackoverflow.com/q/25146643/2751851) – duplode Mar 06 '18 at 20:47
  • 3
    For what it is worth: if we flip arguments, we have `flip dropWhile = foldr (\x k p -> if p x then k p else x : k (\_ -> False)) (\_ -> [])` – chi Mar 06 '18 at 21:15
  • @chi, wow, pretty unexpected one! now the question is: can it be considered as a valid generalization of fold, or not? Have to think more about it... – Zazaeil Mar 07 '18 at 11:41

1 Answers1

5

It is as you say. The universal property says that g [] = i; g (x:xs) = f x (g xs) iff g = fold f i. This can't apply for a straightforward definition of dropWhile, as the would-be f :: a -> [a] -> [a] depends not just on the element and accumulated value at the current fold step, but also on the whole list suffix left to process (in your words, "xs emerg[es] as an unbound variable"). What can be done is twisting dropWhileso that this dependency on the list suffix becomes manifest in the accumulated value, be it through a tuple -- cf. dropWhilePair from this question, with f :: a -> ([a], [a]) -> ([a], [a]) -- or a function -- as in chi's implementation...

dropWhileFun = foldr (\x k -> \p -> if p x then k p else x : k (const False)) (const [])

... with f :: a -> ((a -> Bool) -> [a]) -> ((a -> Bool) -> [a]).

At the end of the day, the universal property is what it is -- a fundamental fact about foldr. It is no accident that not all recursive functions are immediately expressible through foldr. In fact, the tupling workaround your question brings to the table directly reflects the notion of paramorphism (for an explanation of them, see What are paramorphisms? and its exquisite answer by Conor McBride). At face value, paramorphisms are generalisations of catamorphisms (i.e. a straightforward fold); however, it only takes a slight contortion to implement paramorphisms in terms of catamorphisms. (Additional technical commentary on that might be found, for instance, in Chapter 3 of Categorical Programming With Inductive and Coinductive Types, Varmo Vene's PhD thesis.)

duplode
  • 33,731
  • 7
  • 79
  • 150
  • +1 for you answer, though I have few remarks. First of all, you don't need "*iff* `g = fold f i`. *iff* is redundant simply because having `g` guarantees existence of corresponding `fold f i`, where both `f` and `i` completely determined by `g`'s definition. Please correct me, if I am mistaken here. The other thing is that you showed 2 different ways to implement `dropWhile`, hence the question "are they both can be accepted as valid generalization?" arises... and I don't have any answer yet, got no idea how to reason about this particular problem. – Zazaeil Mar 07 '18 at 11:47
  • 1
    @SerejaBogolubov (1) The iff (or, equivalently, the <=> in your question) is not redundant -- the property is an equivalence, even though the right-to-left direction is trivial. (2) They are both correct implementations of `dropWhile`, so they are both valid. The follow-up question I would suggest instead would be whether `dropWhileFun` can be more straightforwardly expressed through some other recursion scheme, similarly to how `dropWhilePair` can be expressed as a paramorphism. – duplode Mar 07 '18 at 13:52