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

问题描述 投票:2回答:4

我目前正在研究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 underscore.js lodash ramda.js
4个回答
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循环。


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以便以函数方式操作集合。在这些情况下,它无济于事。


3
投票

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

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

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


3
投票

我同意所有答案,因为图书馆既不是必需的,也不是特别有帮助的。 (我是Ramda的作者之一,BTW。)

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

Martin Drautzburg指出的性能问题的原因是,尽管尾部调用优化is specified,但它在任何地方都是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中正常运行。

© www.soinside.com 2019 - 2024. All rights reserved.