在 javascript 函数中使用类似 Haskell 的函数式累加器

Use of functional Haskell-like Accumulator in javascript functions

我目前正在研究 Haskell,我对它的一些特性非常着迷,例如使用累加器的最终递归函数。

问题:

  1. javascript中是否有类似的结构?或者它甚至 在效率方面有意义,因为 javascript 不像 功能如 Haskell?
  2. 是否有像 ramda、lodash 等库支持这种方式 编程
  3. 如果是这样,你会如何写这个例如 javascript:

     power_acc :: Double -> Int -> Double
     power_acc x y = power_acc_h x y 1
    
     power_acc_h :: Double -> Int -> Double -> Double
     power_acc_h x 0 acc = acc
     power_acc_h x y acc = power_acc_h x (y-1) (acc*x)
    

这是javascript中Haskell代码的直接翻译:

function power_acc(x, y) {
    return aux(x,y,1);
    function aux(x, y, acc) {
        if (y == 0)
            return acc;
        else
            return aux(x, y-1, acc*x);
    }
}

Is there any library like ramda, lodash, ... that supports this way of programming?

你不需要 lodash 或 ramda。你可以用你的 普通 javascript 就像我上面展示的那样。另请注意,lodash 是 一个实用程序库,提供一致的 API 用于操作 以功能方式收集。在这些情况下它不会帮助你。

Is there a construct in javascript similar to that?

是的,你可以直接将其翻译成 JS:

function power_acc(x, y) { // Double -> Int -> Double
    y = y>>>0; // cast to positive int (avoiding nontermination)
    return power_acc_h(x, y, 1);
}
function power_acc_h(x, y, acc) { // Double -> Int -> Double -> Double
    return y == 0
      ? acc
      : power_acc_h(x, y-1, acc*x);
}

Or does it even make sense regarding efficiency since javascript is not as functional as Haskell?

在 ES6 中,JS 完全支持尾递归,你将获得与循环相同的效率(甚至可能比 haskell 更好,因为你不会创建惰性乘法)。

Is there any library like ramda, lodash, ... that supports this way of programming

不需要图书馆。尽管我确定有一些库可以简化类型检查或为模式匹配提供更好的表示法。

How would you write this for example in javascript?

您将使用 while 循环。 haskell中的所有累加函数都是这样写的,因为它们可以直接优化成一个循环,这就是你应该在 JS 中对这个结构使用的表示法(因为大多数程序员都熟悉它):

function power_acc(x, y) { // Double -> Int -> Double
    y = y>>>0; // cast to positive int (avoiding nontermination)
    var acc = 1;
    while (y != 0) {
        acc *= x;
        y -= 1;
    }
    return acc;
}

改变局部变量没有坏处,你的函数仍然是纯净的。如果您正在寻找更短的符号,请使用 for 循环。

除了Sibi的回答,我想指出javascript(至少nodejs)实际上分配了堆栈space。它运行良好且速度快到大约 13,000 的指数,然后你会得到 RangeError: Maximum call stack size exceeded。要进行此实验,您需要将基数设置为接近 1 的数字(例如 1.0001),否则您将得到无穷大。

Haskell 不存在这个问题。 1000 倍大的指数(即 13,000,000)仍然不会导致任何 space 问题,尽管 运行 确实需要几秒钟。这是因为递归是尾调用,而这些运行 in constant space in haskell.

所以在某种程度上 Sibi 的回答模仿了 haskell 的表达能力,但它仍然表现出不同的 运行 时间行为。我认为您对此无能为力。

我同意所有的答案,因为图书馆既不是必需的也不是特别有用。 (顺便说一句,我是 Ramda 的作者之一。)

Bergi 对 JS 的翻译很好,尽管我认为至少在浏览器端 JS 中更符合地道,将辅助函数嵌入本地闭包中,更接近 Sibi 的答案。

Martin Drautzburg 指出的性能问题的原因是,尽管尾调用优化 is specified, it's barely implemented 无处不在。一个例外是 Babel 对直接递归的支持,因此 Babel 转译版本应该获得预期的性能优势。

因此,如果您因为优雅而想这样做,并且因为您相信 TCO 很快就会出现,并且如果您不担心当前可能出现的性能问题,那么这些回复很有用,我会甚至再加入一种 ES6 技术:

// Double -> Int -> Double -> Double
function powerAcc(x, y, acc = 1) {
    return y == 0 ? acc : powerAcc(x, y - 1, acc * x);
}

powerAcc(2, 5); //=> 32

Default function parameters 帮助替换这种语言中一些简单形式的模式匹配,它没有真正的模式匹配。这仍然依赖于 TCO,但可以使代码更简洁。它也应该 运行 在 Babel 中表现出色。