Перейти к содержимому

Лекция 4. Функции и замыкания

1. Введение

Функции — фундаментальный строительный блок любой программы. В Swift функции обладают рядом уникальных особенностей: метки аргументов, строгая типизация параметров, inout-параметры и мощная система замыканий. В этой лекции мы рассмотрим все аспекты работы с функциями и замыканиями, сравнивая их с Python.


2. Определение функций

Функция объявляется с помощью func. В отличие от Python, типы параметров и возвращаемого значения обязательны.

func greet(name: String) -> String {
return "Привет, \(name)!"
}
print(greet(name: "Анна")) // Привет, Анна!

Сравнение с Python:

def greet(name: str) -> str:
return f"Привет, {name}!"
print(greet("Анна")) # Привет, Анна!

Обратите внимание: в Swift при вызове указывается имя параметра (name:). В Python аргументы по умолчанию позиционные.

Если функция ничего не возвращает, -> не указывается (фактически возвращается Void):

func sayHello() {
print("Здравствуйте!")
}

3. Возврат нескольких значений через кортежи

Swift позволяет возвращать именованные кортежи — это удобнее, чем result[0]:

func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1...] {
if value < currentMin { currentMin = value }
else if value > currentMax { currentMax = value }
}
return (currentMin, currentMax)
}
let result = minMax(array: [3, 1, 7, -2, 5])
print("Мин: \(result.min), Макс: \(result.max)") // Мин: -2, Макс: 7

В Python возвращают обычный кортеж без именованных полей:

def min_max(array):
return min(array), max(array)
result = min_max([3, 1, 7, -2, 5])
print(f"Мин: {result[0]}, Макс: {result[1]}")

4. Метки аргументов и имена параметров

Уникальная особенность Swift: каждый параметр может иметь два имениметку (при вызове) и имя (внутри функции):

func greet(person name: String, from city: String) -> String {
return "Привет, \(name)! Ты из \(city)."
}
print(greet(person: "Олег", from: "Казань"))

Здесь person и from — метки (видны снаружи), name и city — имена (внутри).

4.1. Пропуск метки с _

Метку можно убрать символом _, приближая вызов к Python-стилю:

func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
print(multiply(4, 5)) // 20 — без меток

5. Параметры по умолчанию

Работают так же, как в Python:

func connect(host: String, port: Int = 8080) {
print("Подключение к \(host):\(port)")
}
connect(host: "localhost") // localhost:8080
connect(host: "server.com", port: 443) // server.com:443

6. Вариативные параметры

Принимают ноль или более значений. Обозначаются ... (аналог *args в Python):

func average(_ numbers: Double...) -> Double {
guard !numbers.isEmpty else { return 0 }
var total: Double = 0
for number in numbers { total += number }
return total / Double(numbers.count)
}
print(average(3.0, 5.5, 7.0, 2.5)) // 4.5

Внутри функции вариативный параметр — это массив [Double].


7. inout-параметры — передача по ссылке

По умолчанию параметры — константы. Чтобы изменить переданное значение, используется inout, а при вызове — символ &:

func swapValues(_ a: inout Int, _ b: inout Int) {
let temp = a; a = b; b = temp
}
var x = 10, y = 20
swapValues(&x, &y)
print("x = \(x), y = \(y)") // x = 20, y = 10

В Python нет прямого аналога для простых типов — обычно используют возврат значений или изменяемые контейнеры.


8. Функции как тип данных первого класса

В Swift, как и в Python, функции можно присваивать переменным, передавать аргументами и возвращать из функций.

8.1. Функциональный тип и присвоение переменной

func addTwo(_ a: Int, _ b: Int) -> Int { return a + b }
func mulTwo(_ a: Int, _ b: Int) -> Int { return a * b }
var operation: (Int, Int) -> Int = addTwo
print(operation(3, 4)) // 7
operation = mulTwo
print(operation(3, 4)) // 12

Тип (Int, Int) -> Int описывает сигнатуру: принимает два Int, возвращает Int.

8.2. Функция как параметр и возвращаемое значение

func apply(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
print(apply(10, 5, operation: addTwo)) // 15
func chooseOp(shouldAdd: Bool) -> (Int, Int) -> Int {
return shouldAdd ? addTwo : mulTwo
}
let op = chooseOp(shouldAdd: false)
print(op(6, 4)) // 24

9. Замыкания (Closures)

Замыкания — анонимные функции, которые можно передавать и использовать в коде. Аналог lambda в Python, но значительно мощнее.

9.1. Полный синтаксис

{ (параметры) -> ТипВозврата in
// тело
}

Пример — сортировка в обратном порядке:

let names = ["Олег", "Анна", "Борис", "Дарья"]
let sorted1 = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(sorted1) // ["Олег", "Дарья", "Борис", "Анна"]

9.2. Захват значений

Замыкания захватывают переменные из окружающего контекста по ссылке:

func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2
print(counter()) // 3

В Python для аналогичного поведения нужно nonlocal:

def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter

10. Сокращённый синтаксис замыканий

Swift позволяет поэтапно сокращать запись замыкания:

let names = ["Олег", "Анна", "Борис", "Дарья"]
// 1. Полный синтаксис
names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
// 2. Вывод типов — компилятор определяет типы из контекста
names.sorted(by: { s1, s2 in return s1 > s2 })
// 3. Неявный return (одно выражение)
names.sorted(by: { s1, s2 in s1 > s2 })
// 4. Сокращённые имена: $0, $1
names.sorted(by: { $0 > $1 })
// 5. Операторная функция
names.sorted(by: >)

11. Trailing Closure Syntax

Если замыкание — последний аргумент, его можно вынести за скобки:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evens = numbers.filter { $0 % 2 == 0 }
print(evens) // [2, 4, 6, 8, 10]

Удобно для цепочек функциональных преобразований:

let result = numbers
.filter { $0 % 2 == 0 } // чётные
.map { $0 * $0 } // квадраты
.reduce(0) { $0 + $1 } // сумма
print(result) // 220

Аналог в Python (идиоматичный вариант):

numbers = list(range(1, 11))
result = sum(x * x for x in numbers if x % 2 == 0)
print(result) # 220

12. Escaping Closures (@escaping)

По умолчанию замыкания non-escaping — не живут дольше функции. Если замыкание нужно сохранить (callback, отложенный вызов), оно помечается @escaping:

var handlers: [() -> Void] = []
func addHandler(_ handler: @escaping () -> Void) {
handlers.append(handler)
}
addHandler { print("Обработчик 1") }
addHandler { print("Обработчик 2") }
for h in handlers { h() }
// Обработчик 1
// Обработчик 2

Внутри @escaping-замыкания обязательно явное self:

class Manager {
var name = "Менеджер"
var tasks: [() -> Void] = []
func addTask(_ task: @escaping () -> Void) {
tasks.append(task)
}
func addGreeting() {
addTask { print("Привет от \(self.name)") }
}
}

В Python такого разделения нет — все функции по природе escaping.


13. Autoclosures (@autoclosure)

@autoclosure автоматически оборачивает выражение в замыкание, обеспечивая отложенное вычисление:

func logIfTrue(_ condition: @autoclosure () -> Bool, message: String) {
if condition() { print("LOG: \(message)") }
}
logIfTrue(2 > 1, message: "Два больше одного")
// LOG: Два больше одного

Без @autoclosure пришлось бы писать { 2 > 1 }. Используется в стандартной библиотеке — например, assert:

assert(array.count > 0, "Массив не должен быть пустым")

14. Сравнение: Swift vs Python

АспектSwiftPython
Определение функцииfunc name(params) -> Typedef name(params):
Типизация параметровОбязательнаяОпциональная
Метки аргументовДа (уникальная фича)Нет
Вариативные параметрыType...*args
Передача по ссылкеinout + &Через изменяемые объекты
Анонимная функция{ params in body }lambda params: expr
Многострочная анонимнаяДаНет (lambda — одно выражение)
Сокращённые аргументы$0, $1Нет
Trailing closureДаНет
Escaping closures@escapingВсе callable — escaping
Autoclosures@autoclosureНет аналога

15. Упражнения

Упражнение 1. Напишите функцию power(base:exponent:), которая возводит число в степень. Значение exponent по умолчанию — 2.

print(power(base: 3)) // 9
print(power(base: 2, exponent: 10)) // 1024

Упражнение 2. Напишите функцию joinStrings, принимающую вариативный параметр строк и разделитель (по умолчанию ", "), возвращающую объединённую строку.

print(joinStrings("Swift", "Python", "Go")) // Swift, Python, Go
print(joinStrings("a", "b", "c", separator: " - ")) // a - b - c

Упражнение 3. Напишите функцию applyToEach, принимающую inout-массив [Int] и замыкание (Int) -> Int, применяющее преобразование к каждому элементу.

var nums = [1, 2, 3, 4, 5]
applyToEach(&nums) { $0 * $0 }
print(nums) // [1, 4, 9, 16, 25]

Упражнение 4. Напишите makeMultiplier(factor:), возвращающую замыкание, умножающее число на factor.

let triple = makeMultiplier(factor: 3)
print(triple(7)) // 21
print(triple(10)) // 30

Упражнение 5. Используя map, filter, reduce и trailing closure, обработайте массив 1…20: отберите числа, делящиеся на 3, возведите в квадрат, найдите сумму.


16. Вопросы для самопроверки

  1. Чем отличается метка аргумента от имени параметра? Приведите пример.
  2. Для чего используется _ перед именем параметра?
  3. Как вариативные параметры (Type...) соотносятся с *args в Python?
  4. Что такое inout-параметр и зачем при вызове нужен &?
  5. Что означает тип (String, Int) -> Bool?
  6. Перечислите этапы сокращения синтаксиса замыкания.
  7. Что такое trailing closure и когда его удобно использовать?
  8. В чём разница между @escaping и non-escaping замыканиями?
  9. Для чего нужен @autoclosure?
  10. Почему замыкания в Swift мощнее, чем lambda в Python?

17. Итоги

В этой лекции мы изучили:

  • Определение функций с обязательной типизацией параметров и возвращаемых значений.
  • Метки аргументов — уникальную фичу Swift, повышающую читаемость.
  • Параметры по умолчанию и вариативные параметры для гибких интерфейсов.
  • inout-параметры для передачи по ссылке с явным &.
  • Функциональный тип — функции как объекты первого класса.
  • Замыкания — анонимные функции с захватом значений.
  • Сокращённый синтаксис: $0/$1, неявный return, trailing closure.
  • @escaping и @autoclosure — специальные атрибуты замыканий.

В следующей лекции мы подробно рассмотрим коллекции и функциональные методы map, filter, reduce, активно используя замыкания на практике.