Привет, Хабр! Представляю вашему вниманию перевод статьи Романа ПровазникаWhy FP matters even for OOP developer?

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

Меня попросили высказать личное мнение о функциональном программировании с объективно-ориентированной точки зрения. Меня даже заставили создать аккаунт на Медиуме. Как экс-ООП разработчик и на данный момент когда-нибудь-я-стану-настоящим-ФП-программистом, я думаю, мне есть что сказать:

Добро пожаловать, путешественник


Если ты не провел последние 60 лет на другой планете (если и так, добро пожаловать обратно, путешественник), ты наверняка слышал о функциональном программировании. Если ты следишь за современными трендами в индустрии программного обеспечения, ты слышишь такие термины как «функциональное программирование», «неизменяемость», «чистые функции» и «композиция» ежедневно. Все это звучит круто, восхитительно и понятно пока ты не задумываешься: «Эй, я же ООП-программист и не могу (или не хочу) менять парадигму. Что функциональное программирование может мне дать? Почему я должен заморачиваться?» Как ФП программист/энтузиаст, пришедший из мира ООП, я хочу поделиться тем, что сам хотел бы знать еще много лет назад. Об этом можно говорить часами, поэтому выберем три самые важные вещи.

Неизменяемость


Традиционным преимуществом ФП является слово, которое обычно занимает как минимум два слайда каждой презентации о функциональном программировании. Серебряная пуля ФП, так ведь? Не совсем. Несмотря на то, что это слово чаще всего используется в основном функциональными программистами, оно не эксклюзивно для ФП. Как ООП разработчик ты можешь достичь (почти) такого же уровня неизменяемости как и ФП разработчик. На самом деле, это достаточно легко, просто нужно взглянуть немного иначе на твои объекты и коллекции. Представь, что ты не изменяешь оригинал, а создаешь новую версию. Добавил элемент в коллекцию? Отлично! Ты не изменил первоначальную коллекцию, вместо этого у тебя просто новая коллекция, содержащая добавленный элемент. Поменял свойство объекта? Здорово! Теперь у тебя новый объект с измененным свойством.

Знаю, знаю — звучит странно. Но это небольшое переключение сознания позволит тебе спать спокойнее. Как только ты поймешь, что твои объекты не могут быть изменены (только скопированы), ты будешь уверен, что никто в команде не сможет переназначить их в каком-либо другом месте. Это как одолжить твою любимую, древнюю аудиокассету Пинк Флойд без страха получить ее обратно перезаписанную с Джастином Бибером. И как бонус, у твоего кода будут полностью отслеживаемые методы, в которых происходят реальные изменения (где создаются новые объекты на основе оригинальных). И, чуть не забыл, если ты C# или Java разработчик, ты уже используешь их, вызывая «ToLower()», «Trim()» на строках, которые изначально неизменяемы. То есть по сути ничего нового.

Чистые функции


Другое модное словосочетание, ошибочно считающееся исключительно ФП. Что такое «чистая функция»? Проще говоря, чистая функция — это функция, которая возвращает одинаковый результат при одинаковом входном значении, без связи с «внешним миром» (операции ввода-вывода, общее состояние и т.п.), также известными как «побочные эффекты». Типичным примером данного вида функции может быть получение длины строки (ты же не будешь подключаться к базе данных чтобы посчитать количество символов в строке?), вычисление синуса и так далее. Какая выгода от этого для ООП? Такая же как и для ФП. Если ты работаешь с чистыми функциями (или методами), ты точно знаешь какой результат получишь для каждого входного значения. И если твой код не зависит от какого-то скрытого состояния, его очень легко протестировать, и тебя не напряжет написать юнит-тест. Мы все знаем, что без взаимодействия с внешним миром наше ПО было бы бесполезным, но есть ощутимая разница между чистым кодом (написанном чистыми функциями/методами) с вводом-выводом только на границах системы и системой, в который каждый метод зависит от внутреннего состояния, обновляемого другими методами.

Еще раз, небольшое переосмысление подхода к вводу-выводу дает все преимущества ФП, плюс тестирование становится насколько простым, что начнет наконец приносить удовольствие.

Декларативное VS Императивное


Мы уже поменяли точку зрения на наш код — мы можем писать более безопасный (неизменяемый) и тестируемый (чистый) код. Теперь настало время пойти чуть дальше и поменять подход к определению того, что наше ПО должно делать. Что еще так значительно отличает ООП от ФП? В функциональном мире мы определяем «что» должно происходить вместо определения «как» должно происходить. Конечно, остается часть программы, которая выполняют ту конкретную операцию, но тут важно сконцентрироваться на выражениях вместо утверждений. И это то, что ты можешь делать в любимом ООП языке.

Например, если тебе знакомы такие вещи, как LINQ (из C#), ты уже знаешь преимущества выражений запросов, когда ты можешь прочитать свой код как «Ага, здесь я беру 10 элементов из коллекции, сортирую их по алфавиту и использую как параметр для следующего метода». Ты наверняка читал тысячи подобных LINQ выражений без необходимости задумываться, что же на самом деле происходит за кулисами. И это самое главное, концентрироваться на том, что реально имеет значение.

Резюме


Я думаю на этом пока достаточно. Я не хочу в итоге продавать ФП как серебряную пулю. Мир не только черный и белый. Правда в том, что для меня ФП невероятно сложно. Почти также сложно, как правильное ООП. Но его определенно стоит попробовать. С моей точки зрения ООП vs ФП — это по большому счету то, как я задумываюсь о коде и его структуре. Как я думаю о зависимостях, побочных эффектах, всём мире ввода-вывода и тестирования. Если подытожить одной мыслью, то «Функциональный подход поможет, вне зависимости от языка, предметной области и платформы.»

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


  1. dev96
    26.06.2018 03:29

    Не убедили)


    1. aikixd
      26.06.2018 09:14

      Если вы используете внедрение зависимостей, то вы врете сами себе.


      1. jetcar
        26.06.2018 10:04

        это как, что не так с ними? ни разу с этим проблем не видел


        1. aikixd
          26.06.2018 10:50

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


          1. jetcar
            26.06.2018 10:55

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


            1. aikixd
              26.06.2018 11:39

              Смысл не в том, что у функции нет зависимостей, а в том, что если зависимости те же, то результат — тот же.


              К примеру функция f(x) = x + 1. Если неважно сколько раз вы вызовите f(1), вы всегда получите 2. А вот функция g(db, str) = db.getByString(str) + 1 может вернуть разный результат в зависимости от состоянии базы данных. То есть вы завязали функцию на внешний мир.


              Зная это, вы захотите переписать программу так, что-бы логика программы не была завязана на внешний мир. Вы переместите доступ к данным на внешний слой, что-бы в ядре программы использовать f(x), которую проще тестировать и контролировать. Так все изменчивые компоненты будут находится за пределами ядра и вы получите луковую архитектуру естественным образом.


              В хаскеле, иначе написать нельзя в принципе. Все типы данных которые завязаны на внешний мир помечены и использовать просто так вместе с обычными данными их нельзя. Их придется преобразовать и если преобразование не получилось, то эти данные вообще в ядро программы не попадут.


              1. jetcar
                26.06.2018 12:15

                ну в каком-то виде я уже пишу используя ФП :D есть классы которые делают чтото с данными, есть те которые берут извне, но не хаскель потому можно всё
                хорошенько смешать, тут разве что разделение по проектам немного помогает не позволяя создавать круговые зависимости и ревью кода


          1. AstarothAst
            26.06.2018 11:08

            Ну, как бы код пишется, что бы делать какую-то работу и сообщать о ней в наш внешний мир. То есть, ради побочных эффектов он и пишется.


            1. aikixd
              26.06.2018 11:41

              Разница в том, где вы эту связь осуществляете. Для этого нужен отдельный компонент и этот компонент не должен попадать в ядро программы.


  1. wlr398
    26.06.2018 04:54

    Слишком лаконичная статья. Здесь же на Хабре можно найти гораздо более объёмные и интересные статьи как про ФП в целом, так и по конкретным языкам типа Эрланга или Хаскеля.


  1. Bojczuk
    26.06.2018 10:23

    «Разбудите меня? лет через сто, и спросите, что сейчас делается на хабре. И я отвечу — рассказывают про ФП и чем оно лучше ООП.»


    1. papercuter Автор
      26.06.2018 10:24

      Собственно в этом смысл статьи. Техники функционального программирования полезны и в ООП.


  1. ionicman
    26.06.2018 10:51

    Ага — Молоток лучше чем отвертка!

    Нельзя сравнивать ФП или ООП — все это — инструменты, и каждый лучше или хуже подходит для конкретных задач, вот и все.

    Прекратите уже сравнивать несравнимое!


    1. papercuter Автор
      26.06.2018 11:31

      Данная статья не сравнивает ФП и ООП. Как раз наоборот, показывает, что можно использовать практики ФП и в ООП и таким образом улучшать код.


      1. AstarothAst
        26.06.2018 11:33

        Как раз доказательств-то и не видать.


        1. papercuter Автор
          26.06.2018 11:36

          А какие доказательства Вам нужны? Что и в ООП можно писать чистые функции и что их легче тестировать? Что и в ООП при неизменяемости состояния кол-во багов ниже, чем когда разные нечистые функции меняет одну общую переменную?


          1. AstarothAst
            26.06.2018 13:30

            А какие доказательства Вам нужны?

            Примеры, очевидно. Плюсы и минусы. То, что абстрактно «можно» и так понятно, вопрос как именно можно, и что из этого получается.


  1. papercuter Автор
    26.06.2018 13:20

    Все-таки хотелось бы понять, почему минусуете? Что неправильного в статье? Или в переводе?


    1. Welran
      26.06.2018 13:47

      В посте неправильно оформлен перевод, для этого существует специальное оформление. А сама статья — я так и не понял Почему? Какая то, если честно, бессмысленная статья. Вряд ли стоила перевода.


      1. papercuter Автор
        26.06.2018 13:58

        В песочнице нет специального оформления для перевода, к сожалению.
        Это краткая статья о ключевых моментах, которые чаще всего упоминаются в ФП, хотя они также имеют смысл и в ООП. Она скорее для новичков, которые пытаются понять в чем суть ФП и в чем отличие от ООП (на деле ФП можно применять в ООП). Я просто решил начать с чего-то, в планах перевод более глубоких статей.


        1. Welran
          26.06.2018 14:08

          Ммм в чем суть то? И в чем отличие? И как можно применить в ООП?


          1. papercuter Автор
            27.06.2018 10:49

            Есть как минимум два ключевых момента, которые называют при описании ФП: чистые функции и неизменяемость. Их не называют при описании других парадигм. В ООП и императивном программировании, напротив, чаще всего происходит изменение состояния объекта (локальной переменной, приватного поля объекта). Данная статья вкратце описывает, что и в ООП можно использовать эти техники. Можно также не менять состояние объектов, можно вообще не использовать циклы с изменением локальных переменных, в современных более-ООП (они чаще мультипарадигменны) языках есть такие методы, как map, filter, reduce и т.п. Свои же функции и методы можно писать в том же стиле, без сайд-эффектов, создавая новые инстансы вместо изменения имеющихся.


            1. AstarothAst
              27.06.2018 11:25

              можно вообще не использовать циклы с изменением локальных переменных

              То есть пользоваться рекурсией не имея никаких гарантий того, что не будет просадок по производительности. В ФП рекурсивные вызовы вылизывают на уровне ядра языка, как основное средство работы, а в какой-нибудь java этим всерьез кто-нибудь занимался? Вот и получается, что можно-то оно может и можно, но если очень хочется, то лучше взять какой-нибудь erlang, а не делать вид, что можно быть чуть-чуть беременным.


              1. papercuter Автор
                27.06.2018 11:33

                Так может и стоит перейти на функциональный язык, чтобы иметь возможность применять эти техники в соответствии с природой языка, натурально? Лично мой бэкграунд — это .NET (C#), но сейчас я не начинаю новых проектов на C#, теперь только F#.


              1. aikixd
                27.06.2018 15:10

                Современный язык неподдерживающий tailcall это моветон.


                1. AstarothAst
                  27.06.2018 20:24

                  Поддерживать и быть оптимизированным специально под него — разные вещи.


                  1. aikixd
                    28.06.2018 12:57

                    Это и есть оптимизация. Позволяет не создавать фрейм для каждого рекурсивного вызова.