Итак, рассмотрим, как работает мой вариант, тот который я предлагал, и как следует его изменить, чтобы он работал так, как мы ожидаем.
def fact = {it -> sleep(100); it ? it * call(it - 1g) : 1g}
fact = fact.memoize()
println fact(70)
println fact(71)
Что же происходит в этом случае "за сценой"? Если скомлировать этот код, и декомпилировать получившийся байткод в Java-код, для простоты понимания, то это будет выглядеть (схематично) примерно так:
class Closure1 extends MemoizedClosure {
call (param) {
bla-bla;
return param * this.call(param-1); // this тут значит Closure1
}
}
bla-bla;
return param * this.call(param-1); // this тут значит Closure1
}
}
}
Т.е. можно видеть, что вызов call() в теле замыкания всегда обращается к тому объекту замыкания, внутри которого (в лексическом контексте которого), он вызывается. Т.е. в нашем случае это всегда Closure1, а не тот враппер, умеющий делать мемоизацию, который возвращает вызов fact.memoize() (да, метод memoize() всегда возвращает новый объъект замыкания).
А как следует правильно записать это мемоизируемо-рекурсивное замыкание? А вот так:
def fact
fact = {it -> sleep(100); it ? it * fact(it - 1g) : 1g}
fact = fact.memoize()
println fact(70)
println fact(71)
fact = fact.memoize()
println fact(70)
println fact(71)
Обратите внимание на выделанное. Мы определяем переменную fact, и присваиваем ей ссылку на создаваемый объект замыкания, после чего создаем враппер для нашего замыкания и присваиваем ссылку на этот враппер переменной fact. И, поскольку внутри тела нашего замыкания мы не вызываем его метод call() напрямую, а вызываем fact(), то у нас возникает возможность направить рекурсию по правильному пути - т.е. все рекурсивные вызовы теперь будет проходить через наш fact (т.е. через враппер с поддержкой мемоизации).
В Java-коде это будет (схематично) выглядеть так:
class Closure1 extends MemoizedClosure {
// эта ссылка будет указывать на fact, т.е. на closure wrapper,
// эта ссылка будет указывать на fact, т.е. на closure wrapper,
// который поддерживает мемоизацию.
private Closure reference
call (param) {
bla-bla;
return param * reference.call(param-1);
}
}
call (param) {
bla-bla;
return param * reference.call(param-1);
}
}
}
Желающие могут запустить исправленный код на http://groovyconsole.appspot.com/ и убедиться в его правильности :).
Комментариев нет:
Отправить комментарий