有没有办法通过高阶函数“包装”递归函数,以便递归调用也被包装? (例如,在每次调用时记录函数的参数。)
例如,假设我们有一个函数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
答案 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);
}
}