I implemented a broken filter function using an anamorphism from recursion-schemes Hackage library:
import Data.Functor.Foldable
xfilter :: (a -> Bool) -> [a] -> [a]
xfilter f = ana $ project . phi f
phi :: (a -> Bool) -> [a] -> [a]
phi f (h : t) | not (f h) = t
phi f l = l
The function is not a faithful implementation of filter: xfilter odd [1..5] works, but xfilter odd [0,0] doesn't. I tried to implement "retries" by using explicit recursion in phi and then reimplemented that with a paramorphism, so I ended with ana . para:
xfilter :: (a -> Bool) -> [a] -> [a]
xfilter f = ana . para $ phi where
phi Nil = Nil
phi (Cons h (t, tt)) | f h = Cons h t
phi (Cons h (t, tt)) = tt
This is satisfactory, but I then tried to express retries explicitly in phi and perform them outside:
xfilter :: (a -> Bool) -> [a] -> [a]
xfilter f = ana $ project . retry (phi f)
phi :: (a -> Bool) -> [a] -> Either [a] [a]
phi f (h : t) | not (f h) = Left t
phi f l = Right l
retry f x = case f x of
Right x -> x
Left x -> retry f x
Right means 'produce a new element' and Left means 'retry with a new seed'.
The signature of phi started to look pretty similar to the first argument of apomorphism specialized for lists:
xxapo :: ([a] -> Prim [a] (Either [a] [a])) -> [a] -> [a]
xxapo = apo
([a] -> Either [a] [a] vs [a] -> Prim [a] [a] (Either [a] [a])
So I wonder is it possible to implement filtering using apomorphisms or other generalized unfolds, or ana . para is the best I can hope for?
I know I can use folds, but the question is specifically about unfolds.
In short: This can't be done. You always have to break down the input list somehow, which you can't accomplish by unfolding alone. You can see that in your code already. You have
retry (phi f), which is equivalent todropWhile (not . f), which recursively consumes an input list. In your case, the recursion is insideretry.We can implement
filterusingana, but the function passed toanawill have to be recursive, as inHowever, we can implement filtering using
parawithout any further recursion:(although this is not what you've been interested in).
So why it works with
catabut not withana?Now how
filterworks: At each step it consumes one element of a list and sometimes it produces an output element (if it satisfies a given predicate).So we see that we can implement
filteras a catamorphism - we consume each element of a list in a finite time.But we can't implement
filterjust as an anamorphism. We can never know whenfilterproduces a new result. We can't describe the production of a next output element using just a finite number of operations. For example, let's takefilter odd (replicate n 0 ++ [1])- it takes O(n) steps to produce the first element1. So there must be some kind of recursion that searches an input list until it finds a satisfying element.