给定一个函数管道(foo,bar,baz)(1,2,3),你如何在javascript中实现它等同于baz(bar(foo(1,2,3))

时间:2015-12-18 20:21:41

标签: javascript functional-programming currying

我目前正在学习javascript。 我遇到了这个问题并尝试使用javacript中的currying来解决它但是无法正确解决它。

给定一个函数pipe(),它将多个函数作为参数并返回一个新函数,将其参数传递给第一个函数,然后将结果传递给第二个函数,然后传递给第三个函数,依此类推,返回最后一个函数的输出。 例如,给定:pipe(foo, bar, baz)(1, 2, 3)等同于baz(bar(foo(1,2,3)))

我将如何在javascript中解决此问题?

6 个答案:

答案 0 :(得分:5)

功能编程很有趣,所以我会在这个问题上采取行动。通常,这个概念称为function composition,它最好用于一元函数(只接受一个参数的函数)。

在最基本的层面上,功能组合看起来像这样

const comp = f => g => x => f (g (x))

使用 comp id 函数开始编写该函数的是该列表的 fold

const id = x => x;

const foldl = f => y => xs =>
  xs.length === 0 ? y : foldl (f) (f (y) (xs[0])) (xs.slice(1));

const compN = fs => foldl (comp) (id) (fs);

我们可以使用

进行此操作
let foo = x => x - 1;
let bar = x => x * 20;
let baz = x => x + 3;

compN ([foo, bar, baz]) (2);
//=> 99

那怎么工作?

compN ([foo, bar, baz])
// returns: x => foo(bar(baz(x)))

因此,当我们在该返回值上调用(2)时,2将作为x传入,然后整个链执行

foo(bar(baz(2)))
foo(bar(5))
foo(100)
//=> 99

作为此实施的结果,您获得compN 3 其他可重复使用的函数,idcompfoldl

然而,你的问题写得有些神奇。您的标准表明函数组合应该在条目中使用多个参数,因此通过引入丑陋的异常来使整个概念混淆。无论如何,这是一种你可以写它的方式

const id = x => x;

const foldl = f => y => xs =>
  xs.length === 0 ? y : foldl (f) (f (y) (xs[0])) (xs.slice(1));

const badComp = f => g => (...xs) => f(g(...xs));

const pipe = fs => foldl (badComp) (id) (fs);

pipe ([x => x * x, (x,y) => x + y]) (2,3); //=> 25

//=> (2+3) * (2+3)
//=> 5 * 5
//=> 25

正如您所看到的,这里的实用性甚至没有大大改善。如果要向函数发送多个参数,则不会将它们作为数组发送。因此,而不是foo(1,2)使用foo([1,2])。这样你就可以使用我在答案的第一部分中描述的更受欢迎的一元函数组合。然后你不必依赖牙痛糖,如休息参数(...xs) =>和传播算子f(g(...xs))

此外,ES6为您提供了解构分配,因此您可以轻松使用一元函数组合方法,并仍然可以编写看似多个参数的方法。可以使用

使用pipe重写compN示例
compN ([x => x * x, ([x,y]) => x + y]) ([2,3]);

所以是的,我的意见显然支持经典的一元功能组合,但你可以使用任何一个让你开心或完成你的家庭作业。

无论如何,这是我自己的2美分。如果您有任何疑问,我很乐意提供帮助。

我不想用compN的初始实现来压倒你,所以我保持foldl函数简单。如果我要把它拿出去,我实际上会更进一步。

const id = x => x;
const comp = f => g => x => f (g (x));
const eq = x => y => y === x;
const prop = x => y => y[x];
const len = prop ('length');
const isEmpty = comp (eq (0)) (len);
const first = xs => xs[0];
const rest = xs => (xs) .slice (1);
const foldl = f => y => xs =>
  isEmpty (xs) ? y : foldl (f) (f (y) (first (xs))) (rest (xs));
const compN = fs => foldl (comp) (id) (fs);

当然它的工作方式相同

compN ([x => x - 1, x => x * 20, x => x + 3]) (2) //=> 99

由于此compN实施,我们为 FREE 获得 9 其他完全可重复使用的功能。这是巨大的,imo。这意味着下次你必须定义另一个函数时,你可能已经完成了这些其他函数所定义的大部分工作。

此外,我选择实现foldl而不是依赖本地Array.prototype.reduce,因为它不仅向您展示如何通过递归实现迭代循环,还因为它需要一个curried的过程。

使用原生foldl的{​​{1}}相当于

Array.prototype.reduce

要么是好的。再次,选择你喜欢的任何一个。如果你最终选择var uncurry = f => (x,y) => f (x) (y); var foldl2 = f => y => xs => xs.reduce(uncurry(f), y); 解决方案,至少你知道如何制作自己的解决方案 ^,^

相当激进的东西。好运,祝你好运。

答案 1 :(得分:3)

首先,要认识到pipe可以传递任意数量的参数。为了解决这个问题,我们需要使用Arguments objectRest Parameter syntax来获取任意数量的参数。请注意,rest参数语法是新的,并不是所有浏览器都支持,因此使用arguments对象会更安全。另请注意,参数类似于数组,但不是数组,因此无法直接调用原型数组方法。

接下来,要认识到将使用任意数量的参数调用第一个函数。为了解决这个问题,我们需要使用所有函数的apply method

Pipe将返回一个函数,以便稍后调用它。

结合这些想法,我们可以构建管道:

function pipe() {
    var fns = arguments;
    return function piping() {
        var val = fns[0].apply(this, arguments);
        for(var i = 1; i < fns.length; i++) {
            val = fns[i](val);
        }
        return val;
    }
}

如果我们测试它:

var foo = function (a, b, c) { return a + b + c; };
var bar = function (x) { return 2 * x; };
var baz = function (y) { return 1 + y; };
pipe(foo, bar, baz)(1, 2, 3);
// returns 13

我们看到pipe返回13,即(1 + (2 * (a + b + c)))

此外,我们可能希望barbaz采用多个参数。改变管道规格可以实现这一目标。添加一个额外的规则,所有传递给管道的函数必须返回下一个函数的参数数组。这会将pipe转换为不同的结果:

baz.apply(this, bar.apply(this, foo.apply(this, arguments)));

让我们重新定义每个函数以获取多个参数,并将一些结果作为数组返回:

var foo = function (a, b, c) { return [a*a, b*b, c*c]; };
var bar = function (a, b, c) { return [a*2, b*2, c*2]; };
var baz = function (a, b, c) { return [a+1, b+1, c+1]; };
pipeArgsAsArrays(foo, bar, baz)(1, 2, 3);
// returns [3, 9, 19]

重建管道以实现这一目标很简单:

function pipeArgsAsArrays() {
    var fns = arguments;
    return function piping() {
        var val = arguments;
        for(var i = 0; i < fns.length; i++) {
            val = fns[i].apply(this, val);
        }
        return val;
    }
}

我们看到pipeArgsAsArrays返回[3, 9, 19],即[a*a*2+1, b*b*2+1, c*c+2+1]

答案 2 :(得分:1)

一种方法是使用闭包和魔术“arguments”对象,它包含传递给函数的参数列表:

function pipe() {                                                                                             
    var pipe_functions = arguments;
    return function(result) {
        for(var i = 0; i < pipe_functions.length; i++) {
            var func = pipe_functions[i];
            result = func(result);
        }
        return result;
    }
}

然后,举个例子,如果我们有......

function foo(s) {
    return "foo" + s;
}
function bar(s) {
    return "bar" + s;
}
function baz(s) {
    return "baz" + s;
}

调用这样的函数:

pipe(foo, bar, baz)("lol");

返回“bazbarfoolol”,它与

完全相同
baz(bar(foo("lol")));

答案 3 :(得分:0)

这样的事情怎么样?:

function pipe() {
  var functions = arguments;
  return function() {
    var result = arguments
    for (var i in functions) {
      result = functions[i](result);        
    }
    return result;
  }
}

答案 4 :(得分:0)

这是一个没有for loop的替代方案,我认为它非常干净且不那么冗长

function pipe(){
  var funs = arguments;
return function(){
    var args = arguments;
    function loop(fns,acc,index){
        if(index < fns.length ){
            return loop(fns,fns[index](acc),index+1);
        }else{
            return acc; 
        }
    }
    return loop(funs,funs[0].apply(this,args),1)
};
}

评估

pipe(function(a,b,c){ return a+b+c;}, function(a){ return a+2; }, function(a){ return 3+a;})(1,2,3)

答案 5 :(得分:0)

或多或少正确的方法,使用具有身份功能的monadic减少/组合。请注意,没有for循环。

// I = Identity function = simply pass through argument.
var I = function (x) {return x};
// Helper function: convert arguments to real array.
var my = function (x) {return Array.prototype.slice.call(x, 0)}

// Our test function.
var add1 = function (x) {return x + 1};

// "Pipe" a list of functions passing the output of each to the next.
function pipe (fs) {
  return fs.reduce(function (f, g) {
    return function () {
      return g(f.apply(this, my(arguments)))
    }}, I /* Using identity function as initial value. */ );
}

// Zero case works fine. This is kinda like a cat in unix. 
var I2 = pipe([]);

// add1 . add1 = add2 :)  Note the slight change in interface (using a list).
var add2 = pipe([add1, add1]);

// Yup works fine.
var add4 = pipe([add2, add2]);

console.log(add4(10)) // 14.