0Pricing
Scala for Backend Engineering & Functional Programming · Урок

Объяснение ленивых вычислений

Вычисляйте значения только при необходимости.

«Объяснение ленивых вычислений» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Объяснение ленивых вычислений
  2. Создание LazyList
  3. Бесконечные потоки
  4. Ленивое извлечение и фильтрация
← Назад к Scala for Backend Engineering & Functional Programming