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

时间:2015-09-21 09:45:55

标签: javascript haskell underscore.js lodash ramda.js

我目前正在研究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 个答案:

答案 0 :(得分: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来操作 集合以功能的方式。在这些情况下,它不会帮助你。

答案 1 :(得分: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循环。

答案 2 :(得分:3)

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

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

所以在某种程度上,Sibi的回答是mimicks具有表现力,但它仍然表现出不同的运行时行为。我不认为你可以做任何事情。

答案 3 :(得分:3)

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

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

Martin Drautzburg指出的性能问题的原因是,虽然尾部调用优化is specified,但它在任何地方都是barely implemented。 Babel对直接递归的支持是一个例外,因此Babel转换后的版本应该会获得预期的性能优势。

因此,如果您想要这样做是因为优雅,并且因为您认为TCO会很快出现,并且如果您不担心当前可能出现的性能问题,那么这些响应很有用,我会&#39 ;甚至会再添加一个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中运行。