A catamorphism can either deconstruct a value
[1,2,3].reduce((acc, x) => acc + x, 0); // 6
or maintain the structure and act like the identity of the underlying type:
[1,2,3].reduce((acc, x) => acc.concat([x]), []); // [1,2,3]
With lists (or arrays in JS) the catamorphism and the fold (of a foldable container) coincide.
However, with trees they do not:
const treeCata = f => ([x, xs]) =>
f(x) (arrMap(treeCata(f)) (xs));
const arrMap = f => xs =>
xs.map((x, i) => f(x, i));
const arrFold = f => acc => xs =>
xs.reduce((acc, x) => f(acc) (x), acc);
const log = x => (console.log(x), x);
const Node = (x, xs) => ([x, xs]);
const Node_ = x => xs => ([x, xs]);
const tree = Node(1, [
Node(2, [
Node(3, []),
Node(4, [])
]),
Node(5, [])
]);
const foo = treeCata(Node_) (tree);
const bar = treeCata(x => xs => x + arrFold(y => z => y + z) (0) (xs)) (tree);
log(foo);
log(bar);
The role as identity works as expected. The deconstruction, however, is a bit more involved. As a matter of fact I need a list fold to conduct it. I can actually see that there is one layer of deconstruction, because otherwise a list fold wouldn't be enough for a non-linear tree. But still having to use another fold seems odd.
I can only guess that this behavior is related to the terse definition of the used catamorphism. It only takes a single case into account the product Tree a = Node a [Tree a]. Maybe it would be more promising if I'd fully embrace the algebraic structure of the type and distingiush Node/Array constructor and the empty array case.
But does this mean treeCata is not a proper catamorphism? What is it lacking?