2

我目前正在研究 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)
    
4

4 回答 4

4

这是 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);
    }
}

有没有像 ramda、lodash 等支持这种编程方式的库?

你不需要 lodash 或 ramda 。正如我在上面显示的那样,您可以使用普通的 javascript 来做到这一点。另请注意,lodash 是一个实用程序库,提供一致的 API 以功能方式操作集合。在这些情况下它不会帮助你。

于 2015-09-21T10:02:35.813 回答
4

javascript中是否有类似的构造?

是的,你可以把它翻译成 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);
}

还是因为 javascript 的功能不如 Haskell,所以它在效率方面是否有意义?

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

有没有像 ramda、lodash 等支持这种编程方式的库

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

例如,您将如何用 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循环。

于 2015-09-21T10:24:47.660 回答
3

除了 Sibi 的回答,我想指出 javascript(至少 nodejs)实际上分配了堆栈空间。它工作正常且快速,指数约为 13,000,然后你会得到RangeError: Maximum call stack size exceeded. 要进行此实验,您需要将基数设置为接近 1 的数字(例如 1.0001),否则您将获得 Infinity。

Haskell 不会遇到这个问题。1000 倍大的指数(即 13,000,000)仍然不会导致任何空间问题,尽管它确实需要几秒钟才能运行。这是因为递归是一个尾调用,并且它们在 haskell 中的恒定空间中运行。

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

于 2015-09-21T10:25:51.810 回答
3

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

Bergi 的 JS 翻译很好,虽然我认为至少在浏览器端的 JS 中更惯用,将辅助函数嵌入到本地闭包中,这更接近 Sibi 的答案。

Martin Drautzburg 指出的性能问题的原因是,虽然指定了尾调用优化,但它几乎没有在任何地方实现。一个例外是 Babel 对直接递归的支持,因此 Babel-transpiled 版本应该获得预期的性能优势。

因此,如果您因为优雅而想这样做,并且因为您相信 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

默认函数参数有助于替换该语言中一些简单形式的模式匹配,这些模式没有真正的模式匹配。这仍然依赖于 TCO,但代码更简洁。它还应该在 Babel 中高效运行。

于 2015-09-21T13:42:27.373 回答