19

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

例如,假设我们有一个函数 ,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
4

7 回答 7

6

我知道这是一种无法回答的问题,但是如果您使用对象和动态分派的方法,您想要做的事情会容易得多。本质上,我们将“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');
于 2014-01-05T14:04:19.743 回答
3
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
于 2014-01-05T14:11:37.277 回答
3

如果您坚持使用常规函数而不使用“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 和日志记录扩展为可变参数,但这会使代码更加复杂。

于 2014-01-05T14:51:07.020 回答
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);
    };
}

这类似于在 JavaScript 中实现 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动态指定 的值。

于 2014-01-05T15:14:19.510 回答
1

如果您无法更改 sum 函数

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,我也会读。

于 2014-01-05T13:23:40.547 回答
1

不修改函数在 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每个函数只使用一次就可以了。

于 2014-01-05T14:32:51.743 回答
-2

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

function logging(fn) {
    var _fn = fn; // local cached copy
    return function(a) {
        console.log(a);
        return _fn(a);
    }
}
于 2014-01-05T13:14:50.237 回答