Высшего порядка функции рекурсивные функции?

есть ли способ "обернуть" рекурсивную функцию через функцию более высокого порядка, чтобы рекурсивный вызов также был обернут? (например, для записи аргументов функции при каждом вызове.)

например, предположим, что у нас есть функция, 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 ответов


давайте начнем назад. Скажем, вы хотите функцию 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));
}

из этой реализации мы можем создать обобщенный :

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 динамически.


Я знаю, что это своего рода не-ответ, но то, что вы хотите, намного проще сделать, если вы используете объекты и динамически отправленные методы. По сути, мы храним "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');

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

Если вы настаиваете на использовании регулярных функций без использования "этого", единственный способ, который я могу придумать, - это применить комбинатор ведения журнала, прежде чем вы свяжете узел с комбинатором рекурсии (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 и ведение журнала, чтобы быть вариативными, но это сделало бы код немного больше сложный.


Если вы не можете изменить функцию сумме

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

тогда это невозможно. Жаль

редактировать

некрасиво, но работает. Не делай этого!--3-->

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, я буду читать его тоже.


это невозможно в JavaScript без изменения функции. Если вы не хотите ручной работы, лучше всего сделать что-то вроде этого:

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

затем вы можете использовать его как:

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 только один раз за функцию, и вы в порядке.


проблема с областью. Попробуйте сделать следующее:

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