Функция более высокого порядка рекурсивных функций?
Есть ли способ "обернуть" рекурсивную функцию через функцию более высокого порядка, чтобы рекурсивный вызов также был обернут? (например, для регистрации аргументов функции для каждого вызова.)
Например, предположим, что у нас есть функция 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
Ответы
Ответ 1
Позвольте начать назад. Предположим, что вам нужна функция 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 combinator в JavaScript:
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
.
Ответ 2
Я знаю, что это не ответ, но то, что вы хотите, намного проще сделать, если вы используете объекты и динамически отправленные методы. По существу, мы сохраняем "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');
Ответ 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
Ответ 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 и вести журнал, чтобы быть переменным, но это сделало бы код немного более сложным.
Ответ 5
Если вы не можете изменить функцию 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, я тоже прочитаю.
Ответ 6
Это невозможно в 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
только один раз для каждой функции, и вы в порядке.
Ответ 7
Проблема. Попробуйте сделать следующее:
function logging(fn) {
var _fn = fn; // local cached copy
return function(a) {
console.log(a);
return _fn(a);
}
}