Я спрошу об этом на примере Scala, но вполне может быть, что это влияет на другие языки, которые допускают гибридные императивные и функциональные стили.
Вот короткий пример ( ОБНОВЛЕНО , см. Ниже):
def method: Iterator[Int] {
// construct some large intermediate value
val huge = (1 to 1000000).toList
val small = List.fill(5)(scala.util.Random.nextInt)
// accidentally use huge in a literal
small.iterator filterNot ( huge contains _ )
}
Теперь iterator.filterNot
работает лениво, и это здорово! В результате мы ожидаем, что возвращенный итератор не будет потреблять много памяти (действительно, O (1)). К сожалению, мы совершили ужасную ошибку: поскольку filterNot
ленив, он сохраняет ссылку на литерал функции huge contains _
.
Таким образом, мы думали, что Для этого метода потребовался бы большой объем памяти во время его работы, и эта память могла быть освобождена сразу после завершения метода, фактически эта память зависает до тех пор, пока мы не забудем возвращенный Iterator
.
(Я только что допустил такую ошибку, которую долго выслеживал! Такие вещи можно поймать по дамам кучи ... )
Как лучше всего избежать этой проблемы?
Похоже, что единственное решение - это тщательно проверять функциональные литералы, которые выживают в конце области и захватывают промежуточные переменные. Это немного неудобно, если вы создаете нестрогую коллекцию и планируете ее вернуть. Может ли кто-нибудь придумать какие-нибудь уловки, специфичные для Scala или другие, которые позволят избежать этой проблемы и позволят мне написать хороший код?
ОБНОВЛЕНИЕ: приведенный ранее пример был глупым, как показывает ответ huynhjl ниже. Это было:
def method: Iterator[Int] {
val huge = (1 to 1000000).toList // construct some large intermediate value
val n = huge.last // do some calculation based on it
(1 to n).iterator map (_ + 1) // return some small value
}
На самом деле, теперь, когда я немного лучше понимаю, как эти вещи работают, я не так беспокоюсь!