Интересный рекурсивный лямбда-пример - PullRequest
1 голос
/ 09 июня 2019

Я только что наткнулся на интересный пример рекурсивных лямбд, и я не очень понимаю, почему это работает таким образом.

rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec 
rec = lambda x: x+1
print(f(10))  

То же в JavaScript.

var rec = function(a) {
  if (a == 0) return 1;
  return rec(a - 1) * a;
}
var f = rec
rec = function(a) {
  return a + 1;
}
console.log(f(10));

К моему удивлению, оба из этих отпечатков 100 вместо 10! (как я и ожидал).

Почему переназначение rec меняет поведение функции f? Когда переменная rec записывается в лямбду, не относится ли она к самой лямбде?


Редактировать. Поскольку большинство ответов объясняют, что происходит, позвольте мне перефразировать вопрос, потому что я ищу более подробное объяснение.

Итак, в момент объявления функции rec в первой строке, почему rec в теле функции не привязывается к себе?

Например, если вы берете JavaScript и переписываете первую строку, по-видимому, «так же», как предложено в одном из полученных ответов:

var rec =function rec(a) {
    if (a == 0) return 1;
    return rec(a - 1) * a;
};
f = rec;

rec = function (a) {
    return a + 1;
}

console.log(f(10));

Этот выводит 10! как и следовало ожидать.

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

Так что я действительно спрашиваю, это механизм, с помощью которого эти языки решают, что связывать переменные в лямбдах.

Я сам пишу переводчик для проекта класса, и я столкнулся с тем же вопросом, когда и где связывать эти переменные. Поэтому я хотел понять, как это работает на популярных языках, чтобы реализовать нечто подобное.

Ответы [ 4 ]

2 голосов
/ 09 июня 2019

Я расскажу об этом для python, потому что это то, с чем я знаком.

Во-первых, такое поведение возможно только потому, что python (и выглядит как javascript, я полагаю) следует позднему связыванию для их замыканий. читать дальше

Позднее связывание - это когда имена внутри замыкания ищутся во время выполнения (в отличие от раннего связывания, когда имена ищутся во время компиляции.)

Это позволяет изменять поведение во время выполнения, связывая переменные, которые ищутся во время выполнения (например, такие функции, как rec).

Последний шаг - это просто преобразование лямбда-функций в эквивалентный синтаксис def, чтобы реальное поведение было более ясным.

код:

rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec 
rec = lambda x: x+1
print(f(10)) 

Может быть эквивалентно:

Во-первых:

def somefunc(x):
    return 1 if x==0 else rec(x-1)*x 

Примечание , python не будет жаловаться на то, что rec не существует даже в чистом сеансе / ядре, потому что он не ищет значение во время определения функции. позднее связывание означает, что если эта функция не вызывается, python не заботится о том, что такое rec.

Тогда:

rec = somefunc
f = rec

def someotherfunc(x):
    return x + 1

f(10) #3628800

Теперь мы изменим rec функцию

rec = someotherfunc

И обратите внимание, что при последующих вызовах функций f будет использоваться запись с поздней привязкой, которая просматривается при вызове функции.

f(10) #100

PS. Полный код добавлен ниже:

def somefunc(x):
    return 1 if x==0 else rec(x-1)*x

rec = somefunc

f = rec

def someotherfunc(x):
    return x + 1

f(10) #3628800

rec = someotherfunc

f(10) #100
2 голосов
/ 09 июня 2019

Вы можете добавить немного console.log и посмотреть, что сначала f вызывается с 10, затем rec с 9, и в результате получается 10 * 10.

var rec = function(a) {
        console.log('f', a);
        if (a == 0) return 1;
        return rec(a - 1) * a;
    };
    f = rec;

rec = function(a) {
    console.log('rec', a);
    return a + 1;
}

console.log(f(10));

Хранение rec.

var rec = function rec(a) {
        console.log('f', a);
        if (a == 0) return 1;
        return rec(a - 1) * a;
    };
    f = rec;

rec = function(a) {
    console.log('rec', a);
    return a + 1;
}

console.log(f(10));
0 голосов
/ 09 июня 2019

Я предлагаю правильно использовать имена переменных, здесь вам не нужно переназначать

почему он использует вторую запись, а не первую?

хорошо, ваш вызов функции происходит после переназначения rec, так что у вас есть последнее значение в rec как

rec = function(a) {
  return a + 1;
}

var f = function(a) {
  if (a == 0) return 1;
  return rec(a - 1) * a;
}

var rec = function(a) {
  return a + 1;
}
console.log(f(10));
0 голосов
/ 09 июня 2019

Эти 3 оператора могут суммировать до одного оператора

rec = lambda x : 1 if x==0 else rec(x-1)*x
f = rec 
rec = lambda x: x+1

от 1 & 2

f = lambda x : 1 if x==0 else rec(x-1)*x

сверху & 3

f = lambda x : 1 if x==0 else x*x
...