Читай, наслаждайся, не душни!
Читай, наслаждайся, не душни!

«Функциональное программирование», «Immutable значения», «Pure функции».
Тоже слышал эти умные слова?

И ты такой: «Что? Можно просто for написать?»

Спокойно. Сейчас все разложим по полочкам. Даже если ты пишешь var a = 1 и гордишься этим - ты свой. Погнали.


Многие думают, что функциональный стиль это просто когда ты используешь map, filter, reduce и чувствуешь себя умным. Типа, "я же не пишу for, значит я уже в functional game". И это…правда. Но только частично.

Функции высшего порядка - лишь вершина айсберга. На деле функциональный стиль это про мышление. Про то, как ты подходишь к задаче, а не просто какими методами бросаешься.

Функциональный стиль (Functional Programming) - такой подход, где:

  • функции как блоки Лего: склеиваешь, передаешь, тасуешь;

  • данные не меняются (immutable);

  • функции честные, без сюрпризов - всегда выдают одно и то же (pure);

  • ты не говоришь «делай цикл», а говоришь «вот что я хочу» (declarative).

Выглядит красиво, звучит страшно. По факту — просто удобно.


Тезис 1: все через map, filter, reduce

Да-да, вот эти магические штуки:

let numbers = [1, 2, 3, 4, 5]

let squares = numbers.map { $0 * $0 }       // [1, 4, 9, 16, 25]
let evens = numbers.filter { $0 % 2 == 0 }  // [2, 4]
let sum = numbers.reduce(0, +) 			    // 15

Перевод на человеческий:
- map: обработай каждую штуку;
- filter: убери лишнее;
- reduce: собери все в одну кучу.

Фокус в том, что эти функции ничего не меняют, они просто принимают, делают магию и возвращают новое значение. Никакой мутации, только передача и результат.

Меньше циклов, меньше багов, больше кайфа.


Тезис 2: let - это любовь

Если ты все еще на var, как на сигаретах - пора бросать.

let name = "Pepe"
// name = "Froggy" // Ага, получай ошибку

Чем меньше ты меняешь - тем меньше ты ломаешь.

let - твой друг. var - бывший, которого пора отпустить.


Тезис 3: Чистые функции (pure) как хороший друг — не подставит

func double(_ x: Int) -> Int {
    x * 2
}

- всегда честно возвращает одно и то же;
- не печатает в консоль;
- не трогает глобальные переменные.
Просто делает свою работу.

Противоположность:

func randomize(_ x: Int) -> Int {
    x * Int.random(in: 1...10)
}

Это уже мутный тип. Как начальник, который обещал повышение.


Тезис 4: Функции как значения (Higher-order functions)

func add(_ a: Int, _ b: Int) -> Int {
    a + b
}

let operation: (Int, Int) -> Int = add
operation(3, 4) // 7

Функции как карты в колоде. Комбинируй, передавай, твори.


Тезис 5: Композиция (composition) - склеивание функций

Маленькие функции можно собрать в одну большую. Как бутерброд.

func double(_ x: Int) -> Int { x * 2 }
func increment(_ x: Int) -> Int { x + 1 }

let result = { double(increment($0)) }
result(3) // 8

Чем проще куски, тем проще их переставлять.


Тезис 6: Типы-значения (value types) > ссылочные (reference types)

Когда ты работаешь со struct (а не с class), ты меньше паришься, кто где что поменял. Каждое изменение - новая копия. Никто не ломает твою память.

struct Point {
    let x: Int
    let y: Int
}

Ты - хозяин объекта, а не какой-то левый ViewController, который взял и поменял тебе «x» в 3 часа ночи.


Тезис 7: Опционалы, Result и map - цепочки без боли

В Swift есть типы, которые позволяют строить цепочки операций, но только если все идет хорошо.

let number: Int? = 5
let doubled = number.map { $0 * 2 } // 10?

Если number - nil, функция внутри map даже не вызовется.

То же самое с Result:

enum MyError: Error {
    case oops
}

let badResult: Result<Int, Error> = .failure(MyError.oops)
let squared = badResult.map { $0 * $0 }

А если Result  это .failure?
Тогда map просто пройдет мимо, не тронув ошибку.

Это как конвейер: если деталь бракованная - просто не обрабатываем, а едем дальше. Никаких истерик.

map, flatMap и другие…позволяют писать код без if let, guard, switch. Они аккуратно работают только с "нормальными" значениями и не трогают плохие.


Знаешь, кто реально кайфует от функционального и декларативного подхода?
Твой любимый SwiftUI.

Он буквально кричит:
"Забудь ты свои UIView, забудь свой delegate, просто скажи, что ты хочешь - и я все сделаю".

struct HelloView: View {
    var body: some View {
        Text("Привет, мир!")
            .font(.title)
            .foregroundColor(.blue)
            .padding()
    }
}

Никаких layoutSubviews(), addSubview() и прочего душного кода. Ты просто описываешь результат, а не процесс. Вот это и есть декларативный стиль.

body - функция, возвращающая описание интерфейса -> pure.
цепочки .font(.title).foregroundColor(.blue).padding() -> чистейший function chaining.


Функциональный стиль это не про "быть модным".

Это про:

  • меньше багов;

  • меньше головной боли;

  • меньше var, for, if let, guard let.

Это как писать код, который сам себя объясняет.

Если тебе мало теории и хочется посмотреть, как функциональный стиль спасает в реальной жизни - залетай в Telegram и следи за обновлениями.

Там я делаю тестовое задание на джуна, где надо:

  • построить граф;

  • пройтись по нему (DFS);

  • построить остовное дерево;

  • применять паттерны проектирования;

  • не забывать про ООП.

Впервой части я писал «чтобы работало».
А во второй — рефакторинг. Переписываем все в функциональном стиле, аккуратно, по‑человечески.


Пример из тестового

На этапе генерации карты нужно было создать массив всех возможных координат комнат.

Типа:

  • пройтись по всем y;

  • внутри по всем x;

  • для каждой пары (x, y) создать Position.

В императивном стиле это выглядело бы так:

for y in 1...mapSize.height {
    for x in 1...mapSize.width {
        positions.append(Position(x: x, y: y))
    }
}

Работает? Да.
Красивая ли это жизнь? Не очень.

Теперь функциональная версия:

let positions = (1...mapSize.height).flatMap { y in
    (1...mapSize.width).map { x in Position(x: x, y: y) }
}

flatMap разворачивает двумерную структуру в плоский массив.
map создает Position для каждой координаты.

Все без единого var, append и посторонних слов.


Второй пример

Задача: прорубить двери между комнатами, чтобы получилась связная карта.
Каждая комната - вершина графа, возможные двери - ребра.
На входе словарь rooms: [Position: Room], по сути самый настоящий граф.
Вот тебе задание: построить остовное дерево (spanning tree) и соединить комнаты.

В первой версии метод выглядел как классический императивный хирург:
он менял комнаты прямо внутри - добавлял двери, мутировал состояния, и после вызова ты уже не знал, кто куда ведет и кто кому что должен.

Работает? Да.
Надежно? Ну…как почтальон, который вскрывает письма.


А потом я подумал: "А давай без боли?"
Переписал на функциональный манер:

func buildSpanningTree(from rooms: [Position: Room]) -> [Position: Room] {
    // возвращает новый словарь с нужными дверями
}

Теперь метод ничего не трогает и ничего не портит.
Он просто принимает данные, делает магию и возвращает результат.

Граф больше не страдает. Он не знал, что над ним делали DFS.
Все чисто, по согласию: один граф на вход, другой на выход.

Это и есть суть функционального подхода:
ты не меняешь объект, ты создаешь новый.


Функциональный стиль это не магия, не религия и не то, что нужно учить ночами.
Это просто способ писать чище, спокойнее и предсказуемо.

Ты все еще можешь любить классы, ООП и даже иногда писать for.
Но когда попробуешь map, flatMap, pure и immutable, назад будет тяжело.

Это как впервые попробовать нормальный кофе. Вроде то же самое, но жить веселее.

Комментарии (14)


  1. RichardMerlock
    11.07.2025 09:50

    Так у меня на сименсе спошная функциональщина, да еще и реалтайм!


  1. aamonster
    11.07.2025 09:50

    Первый пример показателен тем, что в нём первая императивная реализация читается легче, чем "улучшенная" функциональная.


    1. yaSkazalGorbatiy Автор
      11.07.2025 09:50

      В этом есть доля правды :)
      Каждый выбирает свой стиль


  1. OldFisher
    11.07.2025 09:50

    Главная заповедь, которую следует помнить функциональному программисту: "Программы делаются только и исключительно ради побочных эффектов".


    1. Dhwtj
      11.07.2025 09:50

      Функциональное ядро, императивная оболочка


  1. Hippocritters
    11.07.2025 09:50

    В своё время точно так же носились с ООП, как единственно правильным стилем, а до него еще что-нибудь было и лет через 10 будет (а может вернутся к нему, зумеры снова переоткроют что-то с умным видом. Это всё замануха для нового, неискушённого знанием истории поколения.


    1. aamonster
      11.07.2025 09:50

      Ну так и расклад тот же, что с ООП: если ты его освоил и разумно используешь – ты можешь писать программы проще и надёжнее. А если поверил, что это silver bullet и стал пихать во все дыры – твой код может превратиться в тыкву.


  1. HaxeZ
    11.07.2025 09:50

    Не знаком со свифтом, но ежели в нем условный .map() или .filter() не написан через хвостовую рекурсию с возвратом значения, то скорее всего он написан через for, который таки имеет состояние, к тому же мутируемое. Функции-то конечно скорее чистые, чем нет, однако же функциональная парадигма это в первую очередь про отказ от состояния и мутаций.

    Хотя судя по тексту автор за разумное использование функционального программирования, так что с этим можно жить.


    1. aamonster
      11.07.2025 09:50

      А если написан через хвостовую рекурсию, но компилятор её оптимизировал в цикл – это всё равно неправославно?


      1. HaxeZ
        11.07.2025 09:50

        Не знаю, не мне судить. Я адепт быдлокода. Это было скорее про то, кто и насколько привержен трушности парадигмы.

        Как по мне это будет очень показательно указывать на то, что ни объектно-ориентированная, ни функциональная парадигмы в чистом виде не жизнеспособны.

        P. S. По моему скромному опыту ни разу не встречался с тем, чтобы компилятор оптимизировал хвостовую рекурсию, да и вообще любую рекурсию в цикл и, честно говоря, даже не подозревал о подобных кейсах. Видимо по причине того, что я не часто использую рекурсии и не знаю ASM и машинный код, чтобы увидеть, что же там в итоге получится. Пойду почитаю что ль, спасибо, что подтолкнули.


  1. YuryZakharov
    11.07.2025 09:50

    Перевод на человеческий:

    - map: обработай каждую штуку;

    - filter: убери лишнее;

    - reduce: собери все в одну кучу.

    Принципиальная ошибка.

    Следует читать так:

    Перевод на человеческий:- map: каждая штука рбработанна

    filter: убрано лишнее;- reduce: собрано в одну кучу.


  1. tkutru
    11.07.2025 09:50

    Зачем здесь эта GPTщина? Автор, самостоятельно написать статью, в своём стиле, - слабО?


  1. yaSkazalGorbatiy Автор
    11.07.2025 09:50

    Если в Интернете любят обсирать, то на хабре любят обсирать с умным видом :)
    Статья в 2-х словах рассказывает начинающим, что такой стиль вообще есть. В ней нет пропаганды этого стиля, нет сравнения с другими, нигде не написано, что его нельзя применять с ООП и т.д. Нравится такой подход, он применим к твоему проекту - используй.

    Местами мне кажется, что ИТ сообщество хуже ворчливых бабок на лавке...


  1. rukhi7
    11.07.2025 09:50

    Перевод на человеческий:- map: обработай каждую штуку;- filter: убери лишнее;- reduce: собери все в одну кучу.

    так map, filter, reduce это же просто функции, их кто-то должен написать... то есть ФП это программирование для тех, для кого написание функций map, filter, reduce это магия, получается. Получается ФП это такая мечта о программировании без программирования, получается - как бы ничего нового! В самом начале были мечты о том что физики будут писать программы каким-то своим особенным способом не вспоминая про адреса в памяти, про различия типов памяти как памяти программной и памяти области данных, про адреса, указатели, ссылки, загрузки-передачи данных, ...