Замыкания
Захватывайте состояние
«Замыкания» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Что такое замыкание?
Замыкание — это функция, которая захватывает переменные из окружающей области видимости, где она была определена. Она «замыкается» на этих переменных и сохраняет доступ к ним даже после завершения этой области видимости.
object Main {
def main(args: Array[String]): Unit = {
val factor = 3
val multiply = (x: Int) => x * factor
println(multiply(5))
}
}Захват свободной переменной
В выражении x => x * factor x является параметром, а factor — свободной переменной, взятой из внешней области видимости. Функция хранит ссылку на неё.
object Main {
def main(args: Array[String]): Unit = {
val greeting = "Hello"
val greet = (name: String) => s"$greeting, $name!"
println(greet("Ann"))
println(greet("Bob"))
}
}Замыкания переживают свою область видимости
Когда функция возвращает замыкание, захваченные переменные остаются доступными столько же, сколько существует замыкание, даже если определившая его функция уже завершила работу.
object Main {
def makeAdder(n: Int): Int => Int = x => x + n
def main(args: Array[String]): Unit = {
val add10 = makeAdder(10)
val add100 = makeAdder(100)
println(add10(5))
println(add100(5))
}
}У каждого замыкания свой захват
Два вызова фабрики функций создают два независимых замыкания, каждое из которых запоминает собственное захваченное значение.
object Main {
def prefixer(p: String): String => String = s => p + s
def main(args: Array[String]): Unit = {
val withDr = prefixer("Dr. ")
val withMr = prefixer("Mr. ")
println(withDr("Smith"))
println(withMr("Jones"))
}
}Захват изменяемого состояния
Замыкание может захватить var, а затем читать и изменять его. Благодаря этому замыкание может сохранять состояние между вызовами, например значение счётчика.
object Main {
def main(args: Array[String]): Unit = {
var count = 0
val increment = () => { count += 1; count }
println(increment())
println(increment())
println(increment())
}
}Фабрика счётчиков
Объедините эти идеи: функция, возвращающая замыкание со своей собственной приватной переменной var, создаёт инкапсулированный независимый счётчик.
object Main {
def counter(): () => Int = {
var n = 0
() => { n += 1; n }
}
def main(args: Array[String]): Unit = {
val c1 = counter()
val c2 = counter()
println(c1())
println(c1())
println(c2())
}
}Замыкания захватывают ссылки, а не копии
Замыкание захватывает переменную, а не её снимок. Если захваченная переменная var изменится после создания замыкания, замыкание увидит новое значение.
object Main {
def main(args: Array[String]): Unit = {
var msg = "first"
val show = () => println(msg)
show()
msg = "second"
show()
}
}Предпочитайте захват val
Захват неизменяемого val позволяет избежать неожиданностей: захваченное значение никогда не меняется, поэтому замыкание ведёт себя предсказуемо. В функциональном коде отдавайте предпочтение захвату val.
object Main {
def main(args: Array[String]): Unit = {
val taxRate = 0.2
val withTax = (price: Double) => price * (1 + taxRate)
println(withTax(100))
}
}Замыкания и методы коллекций
Лямбда-функции, переданные в map, filter и другие методы, являются замыканиями, если обращаются к переменным извне. В повседневном коде на Scala это встречается постоянно.
object Main {
def main(args: Array[String]): Unit = {
val threshold = 3
val nums = List(1, 2, 3, 4, 5)
val big = nums.filter(_ > threshold)
println(big)
}
}Проблема захвата в циклах
В Scala переменная цикла for при каждой итерации получает новую привязку, поэтому замыкания захватывают разные значения. Это предотвращает классическую ошибку, встречающуюся в некоторых других языках.
object Main {
def main(args: Array[String]): Unit = {
val fns = for (i <- 1 to 3) yield () => i
println(fns.map(f => f()))
}
}Замыкания объединяют поведение и данные
Замыкание объединяет поведение (тело функции) с данными (захваченными переменными). Это простой вариант небольшой объектной структуры с одним методом.
object Main {
def discounter(rate: Double): Double => Double = price => price * (1 - rate)
def main(args: Array[String]): Unit = {
val sale = discounter(0.25)
println(sale(80))
println(sale(200))
}
}Быстрая проверка
Что замыкание захватывает из окружающей области видимости?
Итоги
Вы узнали о замыканиях:
- Замыкание захватывает свободные переменные из внешней области видимости
- Захваченные переменные переживают определившую их функцию
- У каждого экземпляра замыкания есть собственное захваченное состояние
- Замыкания захватывают ссылки, поэтому захваченные изменяемые значения могут меняться
- Лямбда-функции для
map/filter— обычные замыкания
Часто задаваемые вопросы
Урок «Замыкания» бесплатный?
Да — полный текст урока «Замыкания» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 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 структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Замыкания»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?
Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.