Javascript 为什么我需要一个列表折叠来解构一棵树的这个树的变形?

Javascript 为什么我需要一个列表折叠来解构一棵树的这个树的变形?,javascript,recursion,functional-programming,tree,catamorphism,Javascript,Recursion,Functional Programming,Tree,Catamorphism,退化可以解构一个值 [1,2,3].reduce((acc, x) => acc + x, 0); // 6 或维护结构,并像底层类型的标识一样: [1,2,3].reduce((acc, x) => acc.concat([x]), []); // [1,2,3] 对于列表(或JS中的数组),变形和折叠(可折叠容器的)重合 但是,对于树木,它们不会: const treeCata=f=>([x,xs])=> f(x)(arrMap(treeCata(f))(xs)); 常量a

退化可以解构一个值

[1,2,3].reduce((acc, x) => acc + x, 0); // 6
或维护结构,并像底层类型的标识一样:

[1,2,3].reduce((acc, x) => acc.concat([x]), []); // [1,2,3]
对于列表(或JS中的数组),变形和折叠(可折叠容器的)重合

但是,对于树木,它们不会:

const treeCata=f=>([x,xs])=>
f(x)(arrMap(treeCata(f))(xs));
常量arrMap=f=>xs=>
x.map((x,i)=>f(x,i));
常数arrFold=f=>acc=>xs=>
x.reduce((acc,x)=>f(acc)(x),acc);
const log=x=>(console.log(x),x);
常量节点=(x,xs)=>([x,xs]);
常量节点_ux=>xs=>([x,xs]);
常数树=节点(1[
节点(2[
节点(3,[]),
节点(4,[])
]),
节点(5,[])
]);
const foo=treeCata(节点_)(树);
常数条=treeCata(x=>xs=>x+arrFold(y=>z=>y+z)(0)(xs))(树);
日志(foo);

对数(巴)有两种褶皱

  • 用于对数据结构执行归纳的结构折叠
  • 遍历折叠,用于遍历和汇总数据结构的元素
  • 结构折叠直观地用给定函数替换数据结构的每个数据构造函数

    节点(1)([
    节点(2)([
    节点(3)([]),
    节点(4)([]))
    ]),
    节点(5)([]))
    ])
    /*
    |
    |特雷卡塔(fNode)
    v
    */
    fNode(1)([
    fNode(2)([
    fNode(3)([]),
    fNode(4)([]))
    ]),
    fNode(5)([]))
    ])
    
    但是,遍历折叠遍历数据结构的各个元素并对它们进行汇总

    Node_(1)([
        Node_(2)([
            Node_(3)([]),
            Node_(4)([])
        ]),
        Node_(5)([])
    ])
    
    /*
              |
              | foldrTree(add)(0)
              v
    */
    
    [1, 2, 3, 4, 5].reduceRight(add, 0);
    
    在Haskell中,类型类实现了遍历折叠。可折叠类型类的最小完整定义是方法。那么,让我们为树实现它

    //节点:a->[树a]->树a
    const Node_=value=>children=>({value,children});
    //树形图:(a->[b]->b)->树a->b
    const treeCata=fNode=>函数折叠({value,children}){
    返回fNode(值)(children.map(fold));
    };
    //树:树a->[a]
    常量treeElems=treeCata(x=>xss=>[x].concat(…xss));
    //折叠树:((b,a)->b)->b->树a->b
    const foldrTree=cons=>empty=>tree=>
    树元素(tree).还原右(cons,空);
    //加:(数字,数字)->数字
    常数加=(x,y)=>x+y;
    //树:树编号
    常数树=节点(1)([
    节点(2)([
    节点(3)([]),
    节点(4)([]))
    ]),
    节点(5)([]))
    ]);
    
    日志(foldrTree(add)(0)(tree))
    我认为您的
    treeCata
    很好,对于您定义的类型
    treea=Node a[treea]
    是一个有效的亚同构。基本函子是
    treefab=Node a[b]
    ,所有代数都必须处理这个结构。反同构只为您处理递归,而不是节点子节点的列表结构。如果它这样做了,它将对您隐藏信息,您将无法使用初始代数重建树

    当然,您可能希望在
    TreeF
    上定义一些辅助函数。由于节点的非空性,一个特别有趣的问题可能是

    // fold :: Semigroup a => TreeF a a -> a
    // fold1 :: (a -> a -> a) -> TreeF a a -> a
    const fold1 = f => ([x, xs]) => arrFold(f)(x)(xs)
    
    有了它,您确实可以将求和函数定义为

    const treeSum = treeCata(fold1((x, y) => x+y));
    
    正如@Aadit所言,这是一个函数,它允许我们从结构褶皱中导出横向褶皱

    const treeCata=f=>([x,xs])=>
    f([x,arrMap(treeCata(f))(xs)]);
    常量arrMap=f=>xs=>
    x.map((x,i)=>f(x,i));
    常数arrFold=f=>acc=>xs=>
    x.reduce((acc,x)=>f(acc)(x),acc);
    常数treeF_fold1=f=>([x,xs])=>arrFold(f)(x)(xs);
    常量treeSum=treeCata(treeF_fold1(x=>y=>x+y));
    常量节点=(x,xs)=>([x,xs]);
    常量节点_ux=>xs=>([x,xs]);
    常数树=节点(1[
    节点(2[
    节点(3,[]),
    节点(4,[])
    ]),
    节点(5,[])
    ]);
    log(treeCata(Node_)(tree));
    
    console.log(treeSum(tree))
    treeCata
    接收单个函数
    f
    ,该函数是
    节点
    数据构造函数的功能化。因此,我得到了我想要的,即类型为
    [treea]
    的值,而没有
    节点
    层。我应该从一开始就看到这一点。@scriptum这对我来说也不是很明显,我在网上学习了一些其他的例子
    treeCata。fold1
    函数相当于
    foldMap id
    @AaditMShah我故意保留
    fold1
    非货币化。根据我的理解,传递给亚同态的代数只接受一个参数,在
    treeCata
    的情况下,类型为
    TreeF
    的值。(我想把它命名为
    treeF_fold1
    ,但是相邻的'f'疏远了我)@AaditMShah为了实现
    Foldable
    ,我宁愿先用
    treeFmap=f=>treeCata([x,xs])
    分别派生
    Functor
    treeFmap=sconcat=>t=>treeCata(fold1(sconcat))(树形图(f)(t))