递归函数的高阶函数?

时间:2014-01-05 13:10:57

标签: javascript recursion higher-order-functions y-combinator

有没有办法通过高阶函数“包装”递归函数,以便递归调用也被包装? (例如,在每次调用时记录函数的参数。)

例如,假设我们有一个函数sum(),它通过将头部添加到尾部的总和来返回数字列表的总和:

function sum(a) {
    if (a.length === 0) {
        return 0;
    } else {
        return a[0] + sum(a.slice(1));
    }
}

是否有某种方法可以编写高阶函数logging(),它将sum()函数作为输入,并返回一个函数,在每次递归时将参数输出到sum()调用

以下不起作用:

function logging(fn) {
    return function(a) {
        console.log(a);
        return fn(a);
    }
}

sum2 = logging(sum);
sum2([1, 2, 3]);

实际输出:

[1, 2, 3]
-> 6

预期产出:

[1, 2, 3]
[2, 3]
[3]
[]
-> 6

如果重写sum()以便它可以与Y Combinator风格的“递归”一起使用,这是否可行?

function sum_core(g) {
    return function (a) {
        if (a.length === 0) {
            return 0;
        } else {
            return a[0] + g(a.slice(1));
        }
    };
}

sum = Y(sum_core);
sum([1, 2, 3]);
// -> 6

7 个答案:

答案 0 :(得分:7)

我知道这是一种非答案,但如果您使用对象和动态调度的方法,您想要的更容易。基本上,我们将“rec”存储在一个可变的单元格中,而不是必须传递它。

它类似于sum = logging(sum)(而不是sum2 =),但更具惯用性,并且不会对我们发送的可变引用的名称进行硬编码。

var obj = {
  sum : function(a){
    if (a.length === 0) {
      return 0;
    } else {
      return a[0] + this.sum(a.slice(1)); // <-- dispatch on `this`
    }
  }
}

function add_logging(obj, funcname){
   var oldf = obj[funcname];
   obj[funcname] = function(/**/){
     console.log(arguments);
     return oldf.apply(this, arguments);
   }
}

add_logging(obj, 'sum');

答案 1 :(得分:4)

function sum(a) {
    if (a.length === 0) {
        return 0;
    } else {
        return a[0] + sum(a.slice(1));
    }
}

var dummySum = sum, sum = function(args) {
    console.log(args);
    return dummySum(args);
};
console.log(sum([1, 2, 3]));

<强>输出

[ 1, 2, 3 ]
[ 2, 3 ]
[ 3 ]
[]
6

答案 2 :(得分:4)

如果你坚持使用常规函数而不使用“this”,我能想到的唯一方法就是在你使用递归(Y)组合器打结之前应用记录组合器。 基本上,我们需要在记录器中使用动态调度,就像我们在sum函数本身中使用动态调度一样。

// f: function with a recursion parameter
// rec: function without the recursion parameter  

var sum = function(rec, a){
  if (a.length === 0) {
    return 0;
  } else {
    return a[0] + rec(a.slice(1));
  }
};

var logging = function(msg, f){
  return function(rec, x){
    console.log(msg, x);
    return f(rec, x);
  }
}

var Y = function(f){
  var rec = function(x){
    return f(rec, x);
  }
  return rec;
}

//I can add a bunch of wrappers and only tie the knot with "Y" in the end:
console.log( Y(logging("a", logging("b", sum)))([1,2,3]) );

<强>输出

a [1, 2, 3]
b [1, 2, 3]
a [2, 3]
b [2, 3]
a [3]
b [3]
a []
b []
6

我们也可以将Y和日志记录扩展为可变参数,但它会使代码更复杂。

答案 3 :(得分:3)

让我们开始倒退。假设您想要一个函数traceSum

> traceSum([1, 2, 3]);
[1, 2, 3]
[2, 3]
[3]
[]
6

您可以按如下方式实施traceSum

function traceSum(a) {
    console.log(a);
    if (a.length === 0) return 0;
    else return a[0] + traceSum(a.slice(1));
}

从这个实现中我们可以创建一个通用的trace函数:

function trace(f) {
    return function (a) {
        console.log(a);
        return f(trace(f), a);
    };
}

这与在Y中实现Y组合器的方式类似:

function y(f) {
    return function (a) {
        return f(y(f), a);
    };
}

您的traceSum功能现在可以实现为:

var traceSum = trace(function (traceSum, a) {
    if (a.length === 0) return 0;
    else return a[0] + traceSum(a.slice(1));
});

很遗憾,如果您无法修改sum功能,则无法trace,因为您需要某种方式来指定回调动态的功能。考虑:

function sum(a) {
    if (a.length === 0) return 0;
    else return a[0] + sum(a.slice(1));
}

你不能trace上述函数,因为函数sum内部总是会引用函数本身。无法动态指定sum的值。

答案 4 :(得分:2)

如果你不能改变和函数

function sum(a) {
    if (a.length === 0) {
        return 0;
    } else {
        return a[0] + sum(a.slice(1));
    }
}
那么这是不可能的。遗憾

修改

丑陋但有效。不要那样做^^

function rest(a) {
console.log(a);
    sum(a, rest);
}

function sum(a, rest) {
    if (a.length === 0) {
        return 0;
    } else {
        return a[0] + rest(a.slice(1));
    }
}

但看看http://www.nczonline.net/blog/2009/01/20/speed-up-your-javascript-part-2/

搜索memoizer,我也会读它。

答案 5 :(得分:2)

在没有修改功能的情况下,在JavaScript中是不可能的。如果您不希望手动工作,最好的选择是:

function logged(func){
    return eval("("+func.toString().replace(/function(.*){(.*)/g,"function$1{console.log(arguments);$2")+")");
};

然后你就可以使用它:

function sum(a) {
   if (a.length === 0) {
        return 0;
    } else {
        return a[0] + sum(a.slice(1));
    }
}

console.log(logged(sum)([1,2,3,4]));

哪个输出:

{ '0': [ 1, 2, 3, 4 ] }
{ '0': [ 2, 3, 4 ] }
{ '0': [ 3, 4 ] }
{ '0': [ 4 ] }
{ '0': [] }
10

logged本身非常慢,因为它重新编译了函数(使用eval),但生成的函数与手动定义函数一样快。因此,每个功能只使用logged一次就可以了。

答案 6 :(得分:-1)

范围问题。尝试执行以下操作:

function logging(fn) {
    var _fn = fn; // local cached copy
    return function(a) {
        console.log(a);
        return _fn(a);
    }
}