4

我一直在尝试将这个递归的 Haskell 实现翻译为专门用于Lists的 futumorphism

futuL :: (a -> Maybe (b, ([b], Maybe a))) -> a -> [b]
futuL f x = case f x of
  Nothing -> []
  Just (y, (ys, mz)) -> y : (ys ++ fz)
    where fz = case mz of
      Nothing -> []
      Just z -> futuL f z

进入一个命令式 Javascript 循环。这非常困难(至少对我来说):

const None = ({type: "Option", tag: "None"});
const Some = x => ({type: "Option", tag: "Some", runOption: x});

const arrFutu = coalg => x => { // futuL f x
  const acc = []; // ~ fz

  while (true) {
    let optX = coalg(x); // f x

    switch (optX.tag) { // case
      case "None": return acc; // Nothing -> []

      case "Some": {
        let [y, [ys, optX_]] = optX.runOption; // Just (y, (ys, mz))

        switch(optX_.tag) {
          case "None": {
            arrPushFlat(acc) ((ys.unshift(y), ys)); // y : (ys ++ [])
            return acc;
          }

          case "Some": { // y : (ys ++ futuL f z)
            arrPushFlat(acc) ((ys.unshift(y), ys)); 
            x = optX_.runOption;
            break;
          }

          default: throw new UnionError("invalid tag");
        }

        break;
      }

      default: throw new UnionError("invalid tag");
    }
  }
};

我很难在精神上解析 Haskell 代码,尤其是where包含递归调用的部分。我猜这个调用不在尾部位置,因此我的accJS 循环需要一个显式堆栈 ()。

我的翻译正确吗?

4

1 回答 1

3

因为 Haskell 是惰性的,所以可以在计算其余部分之前开始使用“futu”返回的列表的开头。在 Javascript 术语中,最好使用生成器建模。

例如(为简单起见,我使用null值来表示Nones):

const arrFutu = coalg => function*(seed) {
    while (true) {
        const next = coalg(seed);
        if (next) {
            // Maybe (b, ([b], Maybe a)), using null for None & flattening nested tuple   
            const [item, items, nextseed] = next; 
            yield item;
            yield* items;
            // Maybe a, using null for None 
            if (nextseed) { 
                seed = nextseed;
                continue; // Continue iterating if the seed isn't null.
            }
        } 
        return;
    }
}

使用一个示例代数,例如:

const coalg1 = seed => {
    if (seed < 5) {
        return ['a', ['a','a'], seed + 1];
    } else if (seed < 10) {
        return ['b', ['b','b'], seed + 1];
    } else if (seed == 10) {
        return ['c', ['c'], null];
    }
    return null;
}

for (const x of arrFutu(coalg1)(0)) {
    console.log(x);
}

for (const x of arrFutu(coalg1)(20)) {
    console.log(x);
}

让 futu 递归而不是迭代会很好,但似乎Javascript 尾调用优化不适用于 generators


顺便说一句,即使 Haskell 代码不是尾递归的,递归也是“受保护的”:递归调用发生在一个或多个数据构造函数(这里是列表构造函数)中,并且调用可以延迟到构造函数已经“客户在使用返回的列表时被客户剥离。

于 2019-06-01T10:06:23.557 回答