Лекция 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:8080connect(host: "server.com", port: 443) // server.com:4436. Вариативные параметры
Принимают ноль или более значений. Обозначаются ... (аналог *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 = 20swapValues(&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 = addTwoprint(operation(3, 4)) // 7
operation = mulTwoprint(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)) // 249. Замыкания (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()) // 1print(counter()) // 2print(counter()) // 3В Python для аналогичного поведения нужно nonlocal:
def make_counter(): count = 0 def counter(): nonlocal count count += 1 return count return counter10. Сокращённый синтаксис замыканий
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, $1names.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) # 22012. 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
| Аспект | Swift | Python |
|---|---|---|
| Определение функции | func name(params) -> Type | def 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)) // 9print(power(base: 2, exponent: 10)) // 1024Упражнение 2. Напишите функцию joinStrings, принимающую вариативный параметр строк и разделитель (по умолчанию ", "), возвращающую объединённую строку.
print(joinStrings("Swift", "Python", "Go")) // Swift, Python, Goprint(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)) // 21print(triple(10)) // 30Упражнение 5. Используя map, filter, reduce и trailing closure, обработайте массив 1…20: отберите числа, делящиеся на 3, возведите в квадрат, найдите сумму.
16. Вопросы для самопроверки
- Чем отличается метка аргумента от имени параметра? Приведите пример.
- Для чего используется
_перед именем параметра? - Как вариативные параметры (
Type...) соотносятся с*argsв Python? - Что такое
inout-параметр и зачем при вызове нужен&? - Что означает тип
(String, Int) -> Bool? - Перечислите этапы сокращения синтаксиса замыкания.
- Что такое trailing closure и когда его удобно использовать?
- В чём разница между
@escapingи non-escaping замыканиями? - Для чего нужен
@autoclosure? - Почему замыкания в Swift мощнее, чем
lambdaв Python?
17. Итоги
В этой лекции мы изучили:
- Определение функций с обязательной типизацией параметров и возвращаемых значений.
- Метки аргументов — уникальную фичу Swift, повышающую читаемость.
- Параметры по умолчанию и вариативные параметры для гибких интерфейсов.
inout-параметры для передачи по ссылке с явным&.- Функциональный тип — функции как объекты первого класса.
- Замыкания — анонимные функции с захватом значений.
- Сокращённый синтаксис:
$0/$1, неявныйreturn, trailing closure. @escapingи@autoclosure— специальные атрибуты замыканий.
В следующей лекции мы подробно рассмотрим коллекции и функциональные методы map, filter, reduce, активно используя замыкания на практике.