JavaRush /Курси /C# SELF /Вступ до функціонального програмування

Вступ до функціонального програмування

C# SELF
Рівень 51 , Лекція 0
Відкрита

1. Вступ

Функціональне програмування (ФП) — це парадигма програмування, у якій основний будівельний блок — не обʼєкт і не процедура чи метод, а функція у математичному сенсі. У ФП головну увагу приділяють опису «що обчислювати», а не «як обчислювати».

Ви вже стикалися з окремими ідеями ФП, коли працювали з лямбда-виразами та LINQ. Але в чому різниця? На практиці: ООП описує обʼєкти та їхню взаємодію, процедурне програмування — набір кроків, а ФП — композицію функцій, передачу поведінки як значення, відмову від зміни стану (immutability) і відсутність побічних ефектів.

Навіщо взагалі потрібна нова парадигма?

  • Більш чистий, передбачуваний і тестований код.
  • Спрощена підтримка багатопотоковості («немає стану — немає проблем»).
  • Лаконічність і виразність (чим менше коду — тим менше багів).
  • Високорівневі, легко повторно використовувані абстракції.

Аналогія

Уявіть, що ресторан отримав замовлення: «приготувати омлет». Імперативний кухар виконує список інструкцій: взяти яйця, розбити, збити, посмажити. Функціональний кухар каже: res = омлет(яйця) — він оперує функціями, абстрагуючись від внутрішнього стану кухні (ну, майже).

У C# ми можемо використовувати обидва підходи. Це робить мову дуже гнучкою й потужною — особливо для реальних проєктів.

Ключові концепції ФП

1. Функції вищого порядку

Функції можна передавати як параметри, повертати з інших функцій і зберігати в змінних. Ви вже робили це з лямбда-виразами та делегатами. У ФП такі «функції над функціями» — основа всього.

2. Чисті функції

Функція вважається «чистою», якщо її результат залежить тільки від параметрів і вона нічого не змінює поза собою (немає побічних ефектів). Два однакові виклики з однаковими аргументами дають один і той самий результат.

3. Незмінність (Immutability)

Дані не мутують «на місці»: новий стан — це новий обʼєкт. Це суттєво спрощує міркування про програму й допомагає у багатопотоковості.

4. Відсутність побічних ефектів

Функція нічого не пише у файл, не змінює глобальні змінні, не малює на екрані — просто повертає результат. У реальному житті побічні ефекти неминучі, але їх намагаються ізолювати на краях системи.

5. Композиція функцій

Одну функцію можна скласти з інших, як із кубиків. Наприклад: відфільтрувати додатні числа, взяти їхні квадрати й просумувати. Кожна операція — окрема функція, і їх легко комбінувати (WhereSelectSum).

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/ФП-стиль)
foreach (var x in xs) ...
xs.Select(...)
«Мутувати» колекцію Отримати нову колекцію
Стан (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# вас не зупинить: мова дозволяє і те, і інше. Тому в практиках ФП важливо стежити, щоб функція «жила сама по собі», нічого не змінювала назовні й не читала зовні, окрім своїх аргументів.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ