1. Вступ
Функціональне програмування (ФП) — це парадигма програмування, у якій основний будівельний блок — не обʼєкт і не процедура чи метод, а функція у математичному сенсі. У ФП головну увагу приділяють опису «що обчислювати», а не «як обчислювати».
Ви вже стикалися з окремими ідеями ФП, коли працювали з лямбда-виразами та LINQ. Але в чому різниця? На практиці: ООП описує обʼєкти та їхню взаємодію, процедурне програмування — набір кроків, а ФП — композицію функцій, передачу поведінки як значення, відмову від зміни стану (immutability) і відсутність побічних ефектів.
Навіщо взагалі потрібна нова парадигма?
- Більш чистий, передбачуваний і тестований код.
- Спрощена підтримка багатопотоковості («немає стану — немає проблем»).
- Лаконічність і виразність (чим менше коду — тим менше багів).
- Високорівневі, легко повторно використовувані абстракції.
Аналогія
Уявіть, що ресторан отримав замовлення: «приготувати омлет». Імперативний кухар виконує список інструкцій: взяти яйця, розбити, збити, посмажити. Функціональний кухар каже: res = омлет(яйця) — він оперує функціями, абстрагуючись від внутрішнього стану кухні (ну, майже).
У C# ми можемо використовувати обидва підходи. Це робить мову дуже гнучкою й потужною — особливо для реальних проєктів.
Ключові концепції ФП
1. Функції вищого порядку
Функції можна передавати як параметри, повертати з інших функцій і зберігати в змінних. Ви вже робили це з лямбда-виразами та делегатами. У ФП такі «функції над функціями» — основа всього.
2. Чисті функції
Функція вважається «чистою», якщо її результат залежить тільки від параметрів і вона нічого не змінює поза собою (немає побічних ефектів). Два однакові виклики з однаковими аргументами дають один і той самий результат.
3. Незмінність (Immutability)
Дані не мутують «на місці»: новий стан — це новий обʼєкт. Це суттєво спрощує міркування про програму й допомагає у багатопотоковості.
4. Відсутність побічних ефектів
Функція нічого не пише у файл, не змінює глобальні змінні, не малює на екрані — просто повертає результат. У реальному житті побічні ефекти неминучі, але їх намагаються ізолювати на краях системи.
5. Композиція функцій
Одну функцію можна скласти з інших, як із кубиків. Наприклад: відфільтрувати додатні числа, взяти їхні квадрати й просумувати. Кожна операція — окрема функція, і їх легко комбінувати (Where → Select → Sum).
2. ФП у C#: від теорії до практики
C# — багатопарадигмальна мова: вона чудово підтримує ООП, процедурний підхід і потужний функціональний стиль (з лямбдами, делегатами, методами розширення та LINQ).
Розберемо на прикладі нашого навчального застосунку
Уявімо, що ми розвиваємо програму для роботи зі списком чисел і рядків. Наше завдання — застосовувати до цих даних різні операції у функціональному стилі.
Приклад 1: Використання функцій вищого порядку
// Застосовує дію до всіх елементів списку
public static void ForEach<T>(List<T> items, Action<T> action)
{
foreach (var item in items)
{
action(item);
}
}
Використання:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
ForEach(numbers, n => Console.WriteLine(n * n)); // Функція-параметр
Бачите? Функцію можна зберігати у змінній або передавати як звичайне значення — прямо як яблуко на кухні!
Приклад 2: Чиста функція
Функція, яка не змінює стан програми й залежить тільки від вхідних даних:
int MultiplyByTwo(int x)
{
return x * 2;
}
- Не залежить ні від чого зовнішнього.
- Нічого назовні не змінює.
- Для x = 5 завжди поверне 10.
Порівняйте з функцією, яка використовує і змінює глобальну змінну:
int total = 0;
int AddToTotal(int x)
{
total += x;
return total;
}
Це вже не чиста функція — результат залежить від зовнішнього стану, і вона його змінює.
Приклад 3: Незмінність даних
Замість зміни вхідних даних створюємо нові:
List<int> AddOneToEach(List<int> numbers)
{
return numbers.Select(n => n + 1).ToList();
}
Вхідний список узагалі не змінюється. У багатопотокових програмах це особливо зручно: менше блокувань і гонок даних.
Приклад 4: Композиція функцій
Отримати суму квадратів усіх парних чисел:
int SumOfEvenSquares(List<int> numbers)
{
return numbers
.Where(n => n % 2 == 0) // Залишити лише парні
.Select(n => n * n) // Піднести до квадрата
.Sum(); // Просумувати
}
Читабельно й декларативно: кожна операція — окрема функція.
3. Корисні нюанси
ФП, LINQ і C#
LINQ — це майже «ФП на практиці» для колекцій: ви використовуєте функції вищого порядку (Where, Select тощо), отримуєте нові послідовності, не змінюючи вихідні, а кожне перетворення — це окремий вираз. Результат — IEnumerable<T>, який описує, що отримати, а не як ітеруватися.
Таблиця аналогій
| Імперативно (процедурно/ООП) | Функціонально (LINQ/ФП-стиль) |
|---|---|
|
|
| «Мутувати» колекцію | Отримати нову колекцію |
| Стан (total += x) | Чисті функції (xs.Sum()) |
| Описувати як: «зроби те-то» | Описувати: «що ми хочемо отримати» |
ФП vs ООП: два світи — один C#
Це не конкуруючі табори. У реальних C#‑проєктах їх комбінують: модель предметної області (domain model) зручно будувати на класах (ООП), а обробку колекцій, агрегацію даних і трансформації — у функціональному стилі через LINQ, лямбди та методи розширення.
Ваші знання про делегати напряму корисні: Func<T, TResult>, Predicate<T>, Action<T> — це типові будівельні блоки ФП‑стилю.
Універсальна функція фільтрації:
List<T> Filter<T>(List<T> items, Predicate<T> predicate)
{
var result = new List<T>();
foreach (var item in items)
{
if (predicate(item))
result.Add(item);
}
return result;
}
Виклики:
var adults = Filter(people, person => person.Age >= 18);
var bigFiles = Filter(fileNames, name => name.EndsWith(".mp4") && name.Length > 10);
Замість багатьох схожих методів із різними умовами — одна універсальна функція.
Навіщо роботодавцям і на співбесідах ФП‑розробники?
- ФП допомагає тестувати невеликі блоки коду без підняття всієї системи.
- Логіку простіше підтримувати: менше станів — менше джерел помилок.
- Легше писати паралельний і асинхронний код — немає глобального стану, менше гонок даних.
А як «не фанатіти»?
Так, ФП — потужний підхід. Але C# — не суто функціональна мова, і не всі задачі потребують ідеальної чистоти. Не бійтеся локальних змінних і розумної мутації там, де це доречно. Головне — читабельність, передбачуваність і тестованість. Елементи ФП — інструмент, а не релігія.
4. Типові помилки новачка
Легко створити враження функціонального коду, який насправді таким не є.
Наприклад, функція повертає нову колекцію, але всередині дорогою змінює вихідний список — це порушує принцип незмінності й ламає очікування коду, який її викликає.
Ще приклад: лямбда-вираз звертається до зовнішньої змінної й змінює її. У функціональній парадигмі це вважається побічним ефектом і робить поведінку коду менш передбачуваною.
Компілятор C# вас не зупинить: мова дозволяє і те, і інше. Тому в практиках ФП важливо стежити, щоб функція «жила сама по собі», нічого не змінювала назовні й не читала зовні, окрім своїх аргументів.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ