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# компилятор вас не остановит: язык позволяет и то, и другое. Поэтому в практиках ФП важно следить, чтобы функция «жила сама по себе», ничего не меняла вовне и не читала извне, кроме своих аргументов.

2
Задача
C# SELF, 51 уровень, 0 лекция
Недоступна
Функция фильтрации списка
Функция фильтрации списка
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ