Я попрошу об этом на примере 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
}
На самом деле, теперь, когда я немного лучше понимаю, как все это работает, я не так волнуюсь!