1. Повернення кількох значень і Tuple
Іноді задача виявляється занадто тісною для одного результату. Ви читаєте два числа й хочете відразу отримати і мінімум, і максимум. Або ділите число й хочете отримати і частку, і остачу. Можна, звісно, зробити два окремі виклики функцій, але тоді ви подвоюєте роботу, а ризик помилки теж зростає.
У реальній розробці це трапляється постійно: ми обчислюємо основне значення, а поруч нам потрібні корисні додаткові дані — наприклад, статус, межі, кількість або ознака успішності. Саме тут хочеться акуратного способу повернути не одне значення, а невеликий набір.
Tuple як «коробочка» для кількох результатів
Tuple (кортеж) у Swift — це спосіб зібрати кілька значень в одне ціле, не створюючи новий тип. Уявіть, що вам потрібно повернути «пару» або «трійку» результатів — логічно пов’язаних, але не настільки складних, щоб заради них заводити окрему сутність. Тоді tuple — це невеликий контейнер: він зберігає значення поруч, у фіксованому порядку, і дає змогу повернути їх одним return.
Важлива думка: tuple — це саме одне значення, яке повертається, просто всередині нього є кілька частин.
Найпростіший tuple‑результат без імен
Коли ви тільки починаєте, найпростіше побачити tuple на короткому прикладі. Ми хочемо повернути суму й добуток двох чисел — одразу обидва результати.
import Foundation
func sumAndProduct(_ a: Int, _ b: Int) -> (Int, Int) {
let sum = a + b
let product = a * b
return (sum, product)
}
Тут -> (Int, Int) означає: «функція повертає tuple із двох Int». Повертаємо ми його буквально так само: return (sum, product).
Tuple з іменованими елементами
На практиці голі (Int, Int) швидко перетворюються на загадку: «а що там перше? а що друге?». Тому Swift дає змогу надати елементам tuple імена — і це помітно підвищує читабельність.
import Foundation
func sumAndProduct(_ a: Int, _ b: Int) -> (sum: Int, product: Int) {
let sum = a + b
let product = a * b
return (sum: sum, product: product)
}
Тепер результат — це tuple з полями sum і product. Це значно зрозуміліше, ніж .0 і .1 — ми ще дійдемо і до них, не хвилюйтеся.
2. Як працювати з tuple‑результатом
Повернути tuple — це лише половина справи. Друга половина — зручно працювати з тим, що повернулося. Swift дає вам кілька способів: звертатися за індексами .0/.1, звертатися за іменами або розпакувати tuple одразу в кілька змінних.
Кожен спосіб має свій характер: індекси короткі, імена читабельні, а розпакування зручне, коли значення далі живуть окремо. Нижче розберемо всі три, щоб ви могли обирати не за звичкою, а за змістом.
Доступ за індексами .0, .1
Якщо елементи tuple не мають імен, або навіть якщо мають, ви все одно можете звернутися до них за порядковим номером: .0, .1, .2…
import Foundation
let result = sumAndProduct(3, 5)
print(result.0) // 8
print(result.1) // 15
Такий код компілюється і працює, але має мінус: за тиждень або вже завтра зранку ви дивитеся на .0 і щиро не пам’ятаєте, що там було задумано. Це як змінна x1 — тільки гірше: у x1 принаймні є шанс бути першим іксом.
Доступ за іменами
Якщо tuple іменований, код починає читати сам себе: не потрібно тримати в голові порядок.
import Foundation
let r = sumAndProduct(3, 5)
print(r.sum) // 8
print(r.product) // 15
І тут є приємний нюанс: імена елементів — це про читабельність на рівні коду. З погляду компілятора tuple все одно залишається tuple, просто ви звертаєтеся до його частин зрозуміліше.
Розпакування
Іноді вам не потрібен «єдиний об’єкт результату», а потрібні одразу дві змінні. У таких випадках tuple можна розпакувати.
import Foundation
let (sum, product) = sumAndProduct(3, 5) // Тут оголошуються дві змінні
print(sum) // 8
print(product) // 15
Можна розпакувати й частково, якщо одне зі значень вам не потрібне. Для цього використовується _ — «я знаю, що тут є значення, але воно мені не потрібне».
import Foundation
let (sum, _) = sumAndProduct(3, 5)
print(sum) // 8
3. Tuple-результат чи окремі параметри
Коли ви проєктуєте функцію, у вас зазвичай є вибір: повернути один компактний об’єкт — tuple — або передати значення окремо, через параметри, до наступної функції.
Важливо не плутати: окремі параметри зазвичай належать до входу функції, а tuple — до виходу. Але в дизайні коду це все одно вибір: тримати разом чи розносити по частинах.
Сьогодні ми не говоримо про «вихідні параметри», коли функція змінює зовнішню змінну. Ми порівнюємо два нормальні стилі: повертаємо tuple і передаємо його далі або одразу перетворюємо результати на окремі аргументи наступного виклику.
Повернути tuple і передавати далі як одне ціле
Цей стиль доречний, коли значення логічно утворюють набір: вони з’явилися разом, використовуються разом, і вам не хочеться щоразу пам’ятати порядок аргументів.
import Foundation
func minMax(_ a: Int, _ b: Int) -> (min: Int, max: Int) {
if a <= b { return (min: a, max: b) }
return (min: b, max: a)
}
func printMinMax(_ r: (min: Int, max: Int)) {
print("min=\(r.min), max=\(r.max)")
}
І виклик:
import Foundation
let mm = minMax(10, 3)
printMinMax(mm) // min=3, max=10
Плюс цього підходу в тому, що ви не розкидаєте значення по коду. Мінус — іноді tuple живе занадто довго і перетворюється на «пакунок усього на світі», але це вже питання дисципліни.
Рознести значення і передавати як окремі параметри
Цей стиль доречний, коли наступна функція справді сприймає значення окремо, і ви хочете, щоб виклик був максимально явним. Особливо це корисно, коли параметри мають різний зміст і ви хочете підказати його через labels.
import Foundation
func printMinMax(min: Int, max: Int) {
print("min=\(min), max=\(max)")
}
let mm = minMax(10, 3)
printMinMax(min: mm.min, max: mm.max) // min=3, max=10
Тут букв трохи більше, зате у виклику немає жодної загадки: min: і max: стоять на своїх місцях.
Мінітаблиця вибору
У реальності ви змішуватимете обидва підходи. Щоб кожен такий вибір не перетворювався на філософський трактат, зручно тримати просту таблицю-підказку:
| Ситуація | Частіше зручний tuple | Частіше зручні окремі параметри |
|---|---|---|
| Значення логічно «йдуть комплектом» (min/max, quotient/remainder) | Так | Іноді |
| Значення потрібні як один об’єкт, який передається далі | Так | Рідше |
| Виклик має бути максимально самодокументованим на місці | Іноді | Так (labels допомагають) |
| Ви хочете розпакувати значення й працювати зі змінними окремо | Можна, але не обов’язково | Так (за змістом) |
4. Приклад: мінізастосунок «PairAnalyzer»
Щоб тема не залишилася на папері, давайте продовжимо лінію навчального консольного застосунку: програма читає дані, обчислює щось корисне і виводить охайний результат.
Сьогодні ми створимо невеликий інструмент «PairAnalyzer»: він читає два цілі числа і друкує звіт — суму, добуток, мінімум і максимум. Важливо, що обчислення ми винесемо у функцію, яка повертає одразу кілька значень за допомогою одного tuple, а виведення зробимо окремою функцією — щоб не змішувати «обчислити» і «надрукувати».
Контракт застосунку: читання двох чисел
Оскільки введення в консолі — це завжди трохи лотерея: ніколи не знаєш, що введуть люди, — ми використовуємо найпростіший підхід: читаємо рядок і перетворюємо його на Int, а якщо не вдалося — беремо 0.
Це не ідеальна перевірка, але для поточного рівня — аж до теми обробки винятків — цілком нормальний робочий компроміс.
import Foundation
func readInt(prompt: String) -> Int {
print(prompt, terminator: " ")
let line = readLine() ?? ""
return Int(line) ?? 0
}
Зверніть увагу: функція коротка, зрозуміла й має чіткий контракт — повернути Int за будь-яких умов.
Функція, яка повертає одразу 4 результати
Тепер створимо функцію, яка приймає два числа й повертає одразу набір результатів. Ми спеціально використовуємо іменований tuple, щоб код читався без «розшифрування .0/.1».
import Foundation
func analyzePair(_ a: Int, _ b: Int) -> (sum: Int, product: Int, min: Int, max: Int) {
let sum = a + b
let product = a * b
let mm = minMax(a, b)
return (sum: sum, product: product, min: mm.min, max: mm.max)
}
Тут важлива стильова ідея: ми не намагаємося зробити функцію-монстра, яка робить усе сама. Ми використовуємо minMax як маленький будівельний блок — це як LEGO: один кубик не будує замок, але й без кубиків замок не звести.
Окрема функція для друку звіту
Друк — це побічний ефект. Обчислення — чиста логіка. Якщо змішати їх, потім складніше змінювати формат виведення, повторно використовувати обчислення і тестувати код. Тому друк тримаємо окремо.
import Foundation
func printReport(_ r: (sum: Int, product: Int, min: Int, max: Int)) {
print("sum=\(r.sum), product=\(r.product)")
print("min=\(r.min), max=\(r.max)")
}
І тепер код верхнього рівня — тобто те, що реально запускається:
import Foundation
let a = readInt(prompt: "Введіть a:")
let b = readInt(prompt: "Введіть b:")
let report = analyzePair(a, b)
printReport(report)
Якщо запустити програму та ввести 10 і 3, вивід буде приблизно таким:
// Введіть a: 10
// Введіть b: 3
// sum=13, product=30
// min=3, max=10
Невелика схема потоку даних
Іноді корисно побачити, як дані течуть між функціями. Це допомагає не плутати: де вхід, де вихід, що повертається.
flowchart TD
A[виклик readInt для a] --> X[a]
B[виклик readInt для b] --> Y[b]
X --> C[analyzePair]
Y --> C
C --> D[(кортеж звіту)]
D --> E[printReport]
5. Типові помилки
Помилка № 1: спроба повернути різні tuple з різних гілок if/else.
Іноді новачки в одній гілці повертають (min: a, max: b), а в іншій — просто (b, a) або взагалі змінюють порядок. Компілятор Swift досить строгий: тип результату має збігатися. Якщо ви оголосили -> (min: Int, max: Int), то й повертати потрібно саме таку форму з тими самими типами.
Помилка № 2: зловживання .0/.1 і плутанина з порядком.
Доступ за індексами здається швидким, але це типовий «кредит під 0 %»: швидко зараз, боляче потім. Якщо tuple несе зміст (min/max, quotient/remainder), краще витратити дві секунди й дати елементам імена, щоб код читався людською мовою.
Помилка № 3: повертають tuple із 6–7 значень, а потім самі ж у ньому губляться.
Tuple добрий як компактне пакування невеликого набору пов’язаних значень. Але якщо результатів занадто багато, код починає виглядати як «посилка з десяти коробок усередині однієї». На цьому етапі зазвичай час переглянути дизайн: можливо, вам справді не потрібні всі значення або частину обчислень варто винести в окремі функції.
Помилка № 4: здивування, що мітки tuple не впливають на порівняння.
Swift не враховує labels під час порівняння tuple на рівність: якщо типи збігаються, (x: 0, y: 0) і (0, 0) можна порівняти, і вони вважаються рівними за значеннями елементів. Це описано в обговоренні поведінки tuple у Swift Evolution.
Помилка № 5: змішування обчислень і друку в одній функції «про всяк випадок».
Коли функція одночасно обчислює й друкує, її складніше повторно використовувати. Наприклад, ви захочете замість друку повернути рядок або змінити формат звіту — а логіка обчислень уже «впаяна» в print(). Набагато спокійніше тримати обчислення у функції, яка повертає дані (tuple), а виведення — в окремій функції, яка ці дані форматує.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ