在javascript函数中使用函数式Haskell类累加器
我目前正在研究Haskell,我对它的一些特性很感兴趣,例如使用累加器的结束递归函数 问题:在javascript函数中使用函数式Haskell类累加器,javascript,haskell,underscore.js,lodash,ramda.js,Javascript,Haskell,Underscore.js,Lodash,Ramda.js,我目前正在研究Haskell,我对它的一些特性很感兴趣,例如使用累加器的结束递归函数 问题: javascript中是否有类似的构造?甚至是 考虑到效率是有意义的,因为javascript不是 像哈斯凯尔一样有用吗 有像拉姆达、洛达斯这样的图书馆吗。。。这就支持了这种方式 编程语言 如果是这样,您将如何用javascript编写此示例: power_acc :: Double -> Int -> Double power_acc x y = power_acc_h x y 1
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);
}
}
有像拉姆达、洛达斯这样的图书馆吗。。。这就支持了这一点
编程方式
你不需要洛达斯或拉姆达。你可以用你的电脑来做
纯javascript,正如我上面所示。还要注意的是,lodash是
一个实用程序库,为操作提供一致的API
以功能性的方式收集。在这些情况下,它对您没有帮助
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更好,因为您不创建惰性乘法) 有像拉姆达、洛达斯这样的图书馆吗。。。它支持这种编程方式 不需要图书馆。尽管我确信有一些lib可以简化类型检查或为模式匹配提供更好的表示法 您将如何用javascript编写这个示例 您将使用
while
循环。haskell中的所有累积函数都是以这种方式编写的,因为它们可以直接优化为循环,这是JS中这个构造应该使用的符号(大多数程序员都很熟悉):
变异局部变量没有坏处,您的函数仍然是纯函数。如果您想寻找更短的表示法,请使用
for
循环。除了Sibi的答案之外,我想指出javascript(至少是nodejs)实际上分配堆栈空间。当指数达到13000左右时,它运行良好且快速,然后您将得到范围错误:超过了最大调用堆栈大小。要进行此实验,您需要将基数设置为接近1的数字(例如1.0001),否则将得到无穷大
哈斯凯尔没有这个问题。1000倍大的指数(即13000000)仍然不会导致任何空间问题,尽管它确实需要几秒钟才能运行。这是因为递归是尾部调用,它们在haskell的常量空间中运行
因此,在某种程度上,Sibi的回答模仿了Haskell的表达能力,但它仍然表现出不同的运行时行为。我认为你对此无能为力。我同意所有的答案,图书馆既不是必需的,也不是特别有用的。(顺便说一句,我是《拉姆达》的作者之一。)
Bergi到JS的翻译很好,尽管我认为至少在浏览器端JS中,将helper函数嵌入到局部闭包中更为惯用,这与Sibi的答案有点接近
Martin Drautzburg指出性能问题的原因是,尽管尾部调用优化,但它无处不在。一个例外是Babel对直接递归的支持,因此Babel Transpile版本应该获得预期的性能优势
因此,如果您想这样做是因为它的优雅,因为您相信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中运行良好。我的2美分:尽管我非常喜欢函数式编程,但我发现习惯用法累加器+递归对于有状态的for
循环来说是一种不太雅观的编码。在命令式语言中,我更喜欢循环而不是FP习语。当然,有时这样的循环实际上是折叠的,如在发布的示例中,在这种情况下,我发现对于
s或累加器,FP高阶折叠比更优雅。当然,所有这些都只是个人喜好的问题。“对于ES6,JS完全支持尾部递归”。。。然而,这是真的。@ScottSauyet:真的(巴贝尔除外)。使用惯用循环的另一个原因是:-)
// Double -> Int -> Double -> Double
function powerAcc(x, y, acc = 1) {
return y == 0 ? acc : powerAcc(x, y - 1, acc * x);
}
powerAcc(2, 5); //=> 32