Catamorphism associated with a recursive type can be obtained mechanically.
Suppose you have a recursively defined type that has several constructors, each of which has its own fate. I will take the OP example.
data X abf = A Int b | B | C (fa) (X abf) | D a
Then we can rewrite the same type, forcing each of them to be one, regardless of everything. The argument 0 ( B ) becomes one if we add a unit type () .
data X abf = A (Int, b) | B () | C (fa, X abf) | D a
Then we can reduce the number of constructors to one, using Either instead of several constructors. Below we just write infix + instead of Either for short.
data X abf = X ((Int, b) + () + (fa, X abf) + a)
At the level of the term, we know that we can rewrite any recursive definition as the form x = fx where fw = ... writing the explicit equation of the fixed point x = fx . At the type level, we can use the same method for recursive recursive types.
data X abf = X (F (X abf))
Now note that we can autocopy an instance of a functor.
deriving instance Functor (F abf)
This is possible because in the source type, each recursive link is only in a positive position. If this fails, making F abf not a functor, then we cannot have a catamorphism.
Finally, we can write the cata type as follows:
cata :: (F abfw -> w) -> X abf -> w
Is this the type of OP xCata ? It. We need to apply only some type isomorphisms. We use the following algebraic laws:
1) (a,b) -> c ~= a -> b -> c (currying) 2) (a+b) -> c ~= (a -> c, b -> c) 3) () -> c ~= c
By the way, it is easy to remember these isomorphisms if we write (a,b) as the product a*b , unit () as 1 and a->b as the power b^a . Indeed, they become 1) c^(a*b) = (c^a)^b , 2) c^(a+b) = c^a*c^b, 3) c^1 = c .
Anyway, let's start rewriting the part F abfw -> w , only
F abfw -> w =~ (def F) ((Int, b) + () + (fa, w) + a) -> w =~ (2) ((Int, b) -> w, () -> w, (fa, w) -> w, a -> w) =~ (3) ((Int, b) -> w, w, (fa, w) -> w, a -> w) =~ (1) (Int -> b -> w, w, fa -> w -> w, a -> w)
Consider now the full type:
cata :: (F abfw -> w) -> X abf -> w ~= (above) (Int -> b -> w, w, fa -> w -> w, a -> w) -> X abf -> w ~= (1) (Int -> b -> w) -> w -> (fa -> w -> w) -> (a -> w) -> X abf -> w
What is really (renaming w=r ) the desired type
xCata :: (Int -> b -> r) -> r -> (fa -> r -> r) -> (a -> r) -> X abf -> r
The "standard" cata implementation is
cata g = wrap . fmap (cata g) . unwrap where unwrap (X y) = y wrap y = X y
It takes some effort to understand because of its generality, but it really is intended.
About automation: yes, it can be automated, at least in part. There is a recursion-schemes package for hacking that allows one to write something like
type X abf = Fix (F afb) data F abfw = ...
Example:
import Data.Functor.Foldable hiding (Nil, Cons) data ListF ak = NilF | ConsF ak deriving Functor type List a = Fix (ListF a)