Объяснение ленивых вычислений
Вычисляйте значения только при необходимости.
«Объяснение ленивых вычислений» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Что такое ленивые вычисления
В Scala большинство значений являются строгими: они вычисляются сразу после объявления. Ленивые вычисления работают наоборот. Ленивое значение вычисляется только тогда, когда оно впервые понадобится, и никогда раньше.
Это важно, когда вычисления затратны, могут завершиться ошибкой или вообще не понадобиться. Зачем выполнять работу, о которой никто не просил?
Строгий val
Обычный val немедленно вычисляет правую часть, даже если Вы больше никогда её не прочитаете.
Запустите этот код и заметьте, что «computing» выводится до «after». Работа выполняется во время объявления.
object Demo extends App {
val x = { println("computing"); 42 }
println("after")
println(x)
}lazy val
Добавьте lazy перед val, и вычисление отложится до первого обращения.
Здесь сначала выводится «after», а затем «computing» — только при чтении x. Результат также кэшируется: тело выполняется не более одного раза.
object Demo extends App {
lazy val x = { println("computing"); 42 }
println("after")
println(x)
println(x)
}Параметры по имени
Параметр, объявленный как => A, передаётся по имени: аргумент вычисляется только при использовании внутри метода.
Это основа ленивых вычислений. LazyList использует параметры по имени, поэтому хвост никогда не вычисляется, пока его не запросят.
def maybe(cond: Boolean, value: => Int): Int =
if (cond) value else 0
val r = maybe(false, { println("side"); 99 })
// "side" never prints; value was unusedЗнакомство с LazyList
LazyList — ленивая последовательность Scala (в версии 2.13+ она заменила старый Stream). Её голова вычисляется сразу, а хвост — лениво.
Элементы создаются по требованию и сохраняются после вычисления, поэтому одна и та же ячейка никогда не вычисляется дважды.
val ll = LazyList(1, 2, 3)
println(ll)
// prints LazyList(<not computed>) until forcedПринудительное вычисление
LazyList остаётся невычисленным, пока Вы не потребуете его вычисления. Вызов toList, foreach или вывод каждого элемента принудительно вычисляет ячейки.
REPL показывает <not computed> для ленивого хвоста. Принудительное вычисление раскрывает настоящие значения.
object Demo extends App {
val ll = LazyList(1, 2, 3)
println(ll.toList)
}Оператор #::
Добавить элемент в начало LazyList можно с помощью #::. В отличие от :: списка, правый операнд #:: передаётся по имени, поэтому хвост не создаётся, пока не понадобится.
Это позволяет описывать последовательности, остальная часть которых вычисляется лениво.
val ll = 1 #:: 2 #:: 3 #:: LazyList.empty
println(ll.head)Мемоизация
После вычисления ячейки LazyList её значение сохраняется. Повторное чтение ничего не стоит, а выражение, создавшее значение, не выполняется дважды.
Запустите этот код: «calc» выводится только при первом обращении к каждому принудительно вычисленному элементу, но не при повторном чтении.
object Demo extends App {
val ll = LazyList(1, 2).map { n =>
println("calc " + n); n * 10
}
println(ll.head)
println(ll.head)
}Ленивый хвост в действии
Поскольку хвост вычисляется лениво, Вы можете применить map к LazyList, и будут вычислены только элементы, к которым Вы обращаетесь.
Здесь «map» выводится только один раз, хотя список содержит три элемента, потому что принудительно вычисляется только голова.
object Demo extends App {
val r = LazyList(1, 2, 3).map { n =>
println("map " + n); n + 1
}
println(r.head)
}Преимущества ленивых вычислений
Ленивые вычисления позволяют делать три вещи: избегать ненужной работы, создавать бесконечные последовательности и отделять описание вычисления от объёма фактически выполняемой работы.
Вы один раз записываете весь конвейер, а потребитель решает, сколько элементов получить.
Предупреждение
У ленивых вычислений есть издержки. LazyList использует мемоизацию, поэтому ссылка на его голову удерживает в памяти все вычисленные элементы. Для огромных или бесконечных данных не сохраняйте ссылку на голову.
Кроме того, побочные эффекты в ленивом коде выполняются в неожиданные моменты, что может затруднить отладку.
Быстрая проверка
Проверьте своё понимание ленивых вычислений.
Итоги
При строгих вычислениях работа выполняется сразу, а при ленивых откладывается до необходимости. lazy val и параметры по имени (=> A) — основные средства для этого.
LazyList строится на их основе: строгая голова, ленивый мемоизированный хвост, принудительно вычисляемый через toList или при обращении к элементу. Далее мы создадим LazyLists вручную.
Часто задаваемые вопросы
Урок «Объяснение ленивых вычислений» бесплатный?
Да — полный текст урока «Объяснение ленивых вычислений» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Чему я научусь в уроке «Объяснение ленивых вычислений»?
Вычисляйте значения только при необходимости. Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?
Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Объяснение ленивых вычислений»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?
Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Объяснение ленивых вычислений
- Создание LazyList
- Бесконечные потоки
- Ленивое извлечение и фильтрация