ФП vs ООП
Не так давно на хабре появилось несколько постов противопоставляющих функциональный и объектный подход, породивших в комментариях бурное обсуждение того, что вообще это такое — объектно ориентированное программирование и чем оно отличается от функционального. Я, пусть и с некоторым опозданием, хочу поделиться с окружающими тем, что думает по этому поводу Роберт Мартин, также известный, как Дядюшка Боб.
За последние несколько лет мне неоднократно доводилось программировать в паре с людьми, изучающими Функциональное Программирование, которые были предвзято настроены по отношению к ООП. Обычно это выражалось в формe утверждений типа: “Ну это слишком похоже на что-то объектное.”
Я думаю это происходит из убеждения, что ФП и ООП взаимно исключают друг друга. Многие похоже думают, что если программа функциональная, то она не объектно ориентированная. Полагаю, формирование такого мнения — логичное следствие изучения чего-то нового.
Когда мы берёмся за новую технику, мы часто начинаем избегать старых техник, которые использовали раньше. Это естественно, потому что мы верим, что новая техника “лучше” и следовательно старая техника наверное “хуже”.
В этом посте я обосную мнение, что хотя ООП и ФП ортогональны, это не взаимно исключающие понятия. Что хорошая функциональная программа может (и должна) быть объектно ориентированной. И что хорошая объектно ориентированная программа может (и должна) быть функциональной. Но для того, чтобы это сделать, нам придётся определиться с терминами.
Что такое ООП?
Я подойду к вопросу с редукционистских позиций. Есть много правильных определений ООП которые покрывают множество концепций, принципов, техник, паттернов и философий. Я намерен проигнорировать их и сосредоточиться на самой соли. Редукционизм тут нужен из-за того, что всё это богатство возможностей, окружающее ООП на самом деле не является чем-то специфичным для ООП; это просто часть богатства возможностей встречающихся в разработке программного обеспечения в целом. Тут я сосредоточусь на части ООП, которая является определяющей и неустранимой.
Посмотрите на два выражения:
Никакой семантической разницы явно нет. Вся разница целиком и полностью в синтаксисе. Но одно выглядит процедурным, а другое объектно ориентированным. Это потому что мы привыкли к тому, что для выражения 2. неявно подразумевается особая семантика поведения, которой нет у выражения 1. Эта особая семантика поведения — полиморфизм.
Когда мы видим выражение 1. мы видим функцию f, которая вызывается в которую передаётся объект o. При этом подразумевается, что есть только одна функция с именем f, и не факт, что она является членом стандартной когорты функций, окружающих o.
С другой стороны, когда мы видим выражение 2. мы видим объект с именем o которому посылают сообщение с именем f. Мы ожидаем, что могут быть другие виды объектов, котоые принимают сообщение f и поэтому мы не знаем, какого конкретно поведения ожидать от f после вызова. Поведение зависит от типа o. то есть f — полиморфен.
Вот этот факт, что мы ожидаем от методов полиморфного поведения — суть объектно ориентированного программирования. Это редукционистское определение и это свойство неустранимо из ООП. ООП без полиморфизма это не ООП. Все другие свойства ООП, такие как инкапсуляция данных и методы привязанные к этим данным и даже наследование имеют больше отношения к выражению 1. чем к выражению 2.
Программисты, использующие Си и Паскаль (и до некоторой степени даже Фортран и Кобол) всегда создавали системы инкапсулированных функций и структур. Чтобы создать такие структуры даже не нужен объектно ориентированный язык программирования. Инкапсуляция и даже простое наследование в таких языках очевидны и естественны. (В Си и Паскале более естественно, чем в других)
Поэтому то, что действительно отличает ООП программы от не ООП программ это полиморфизм.
Возможно вы захотите возразить, что полифорфизм можно сделать просто используя внутри f switch или длинные цепочки if/else. Это правда, поэтому мне нужно задать для ООП ещё одно ограничение.
Использование полиморфизма не должно создавать зависимости вызывающего от вызываемого.
Чтобы это объяснить, давайте ещё раз посмотрим на выражения. Выражение 1: f(o), похоже зависит от функции f на уровне исходного кода. Мы делаем такой вывод потому что мы также предполагаем, что f только одна и что поэтому вызывающий должен знать о вызываемом.
Однако, когда мы смотрим на Выражение 2. o.f() мы предполагаем что-то другое. Мы знаем, что может быть много реализаций f и мы не знаем какая из этих функций f будет вызвана на самом деле. Следовательно исходный код, содержащий выражение 2 не зависит от вызываемой функции на уровне исходного кода.
Если конкретнее, то это означает, что модули (файлы с исходным кодом), которые содержат полиморфные вызовы функций не должны ссылаться на модули (файлы с исходным кодом), которые содержат реализацию этих функций. Не может быть никаких include или use или require или каких-то других ключевых слов, которые создают зависимость одних файлов с исходным кодом от других.
Итак, наше редукционистское определение ООП это:
Техника использующая динамический полиморфизм чтобы вызывать функции и не создающая зависимостей вызвающего от вызываемого на уровне исходного кода.
Что такое ФП?
И опять я буду использовать редукционистский подход. У ФП есть богатые традиции и история, корни которых глубже, чем само программирование. Существуют принципы, техники, теоремы, философии и концепции, которыми пронизана эта парадигма. Я всё это проигнорирую и перейду сразу к самой сути, к неотъемлемому свойству которое отделяет ФП от других стилей. Вот оно:
f(a) == f(b) если a == b.
В функциональной программе вызов функции с тем же аргументом даёт тот же результат независимо от того, как долго работала программа. Это иногда называют референциальной прозрачностью.
Из сказанного выше вытекает следствие, что f не должна менять части глобального состояния, которые влияют на поведение f. Более того, если мы скажем, что f представляет все функции в системе — то есть все функции в системе должны быть референциально прозрачными — тогда ни одна функция в системе не может изменить глобальное состояние. Ни одна функция не может сделать ничего, что может привести к тому, что другая функция из системы вернёт другое значение при тех же аргументах.
У этого есть и более глубокое следствие — ни одно именованное значение нельзя менять. То есть оператора присваивания нет.
Если тщательно обдумать это утверждение, то можно прийти к заключению, что программа, состоящая только из референциально прозрачных функций ничего не может сделать — так как любое полезное поведение системы меняет состояние чего-нибудь; даже если это просто состояние принтера или дисплея. Однако, если из требований к референциальной прозрачности исключить железо и все элементы окружающего мира оказывается, что мы можем создавать очень полезные системы.
Фокус, конечно, в рекурсии. Рассмотрим функцию которая принимает структуру с состоянием в качестве аргумента. Этот аргумент состоит из всей информации о состоянии, которая нужна функции для работы. Когда работа окончена, функция создаёт новую структуру с состоянием, содержимое которой отличается от предыдущей. И последним действием функция вызывает саму себя с новой структурой в качестве аргумента.
Это только один из простых трюков, которые фукциональная программа может использовать чтобы хранить изменения состояния без необходимости изменять состояние [1].
Итак, редукционистское определение функционального программирования:
Референциальная Прозрачность — переприсваивать значения нельзя.
ФП против ООП
К этому моменту и сторонники ООП и сторонники ФП уже смотрят на меня через оптические прицелы. Редукционизм не лучший способ завести друзей. Но иногда он полезен. В данном случае, я думаю что полезно пролить свет на никак не утихающий холивар ФП против ООП.
Ясно, что два редукционистских определения, которые я выбрал, совершенно ортогональны. Полиморфизм и Референциальная Прозрачность не имеют никакого отношения друг к другу. Они никак не пересекаются.
Но ортогональность не подразумевает взаимного исключения (спросите Джеймса Клерка Максвелла). Вполне можно создать систему, которая использует и динамический полиморфизм и референциальную прозрачность. Это не только возможно, это правильно и хорошо!
Почему эта комбинация хороша? По точно тем же причинам, что оба её компонента! Системы построенные на динамическом полиморфизме хороши, потому что они обладают низкой связностью. Зависимости можно инвертировать и расположить по разные стороны архитектурных границ. Эти системы можно тестировать используя Моки и Фейки и другие виды Тестовых Дублей. Модули можно модифицировать не внося изменения в другие модули. Поэтому такие системы проще изменять и улучшать.
Системы, построенные на референциальной прозрачности тоже хороши, потому что они предсказуемы. Неизменяемость состояния делает такие системы проще для понимания, изменения и улучшения. Это значительно уменьшает вероятность возникновения гонок и других проблем, связанных с многопоточностью.
Главная мысль тут такая:
Нет никакого холивара ФП против ООП
ФП и ООП хорошо работают вместе. И то и другое хорошо и правильно использовать в современных системах. Система, которая построена на комбинации принципов ООП и ФП максимизирует гибкость, поддерживаемость, тестируемость, простоту и прочность. Если убрать одно ради добавления другого это только ухудшит структуру системы.
[1] Так как мы используем машины с архитектурой Фон Неймана мы предполагаем, что в них есть ячейки памяти, состояние которых на самом деле изменяется. В механизме рекурсии, который я описал, оптимизация хвостовой рекурсии не даст создавать новые стекфреймы и будет использоваться первоначальный стекфрейм. Но это нарушение референциальной прозрачности (обычно) скрыто от программиста и ни на что не влияет.
- Программирование
- Java
- Совершенный код
- Функциональное программирование
Чем функциональное программирование отличается от объектно ориентированного
Если вы хотите инициализировать ученика, вы можете сделать что-то вроде этого:
Purity » от xkcd распространяется под лицензией CC BY-NC 2.5.» />
Когда у вас есть такие маленькие, «чистые» функции, то использовать их повторно намного проще, чем вашу традиционную объектно-ориентированную программу. Это спорный подход (если вы посмотрите «повторное использование в функциональном программировании», вы найдете много дискуссий и подкастов на эту тему), так что, я думаю, вы согласитесь со мной в том, что когда вы хотите повторно использовать класс в ООП и добавить функцию, вы добавляете условия и параметры, и ваши функции становятся больше. Ваши абстрактные классы и интерфейсы становятся довольно надежными. Вы должны уделять пристальное внимание более крупной архитектуре приложения из-за побочных эффектов и других факторов, которые повлияют на вашу программу (как мы говорили ранее).
В функциональном программировании все наоборот: ваши функции становятся меньше и гораздо более подходят для того, что вы хотите получить. Каждая функция делает что-то одно, и всякий раз, когда вы захотите сделать что-то одно, вы будете использовать конкретную функцию.
Конечно, в каждой системе есть исключения, но обычно это то, что вы видите в различных базах исходного кода во всем мире!
Ладно, ладно, я заинтригован. Как начать?
Если вы уже хорошо разбираетесь в JavaScript или Python, вы можете сразу приступить к изучению концепций функционального программирования, о которых мы говорили здесь. Если вы хотите больше узнать о «чистых» языках, предназначенных для функционального программирования, вы можете попробовать семейство Lisp (включая Common Lisp, Scheme и Clojure), семейство ML (включая OCaml и F#), Erlang, Elixir, Elm или Haskell.
Функциональное программирование может немного сбивать с толку, пока вы не привыкнете к нему. Но если вы дадите ему шанс и попробуете, ваше программное обеспечение будет надежным, его будет легче отлаживать, и вы будете наслаждаться гарантиями, которые дает прочная основа функционального программирования!
Материалы по теме
- Функциональное программирование: рефакторинг, замыкания и функции высшего порядка
- Функциональное программирование и его применение в JavaScript
Источники
Когда применять функциональное программирование, а когда ООП — отвечают эксперты
ООП или функциональное программирование? А может, всё сразу? Узнаём у экспертов, когда нужно применять ту или иную парадигму программирования.
Многие слышали про функциональное программирование и, возможно, задавались вопросом: «А зачем оно, когда есть ООП?». Мы спросили у экспертов, когда стоит использовать ту или иную парадигму.
Когда применять функциональное программирование, а когда ООП?
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
На данный момент этот блок не поддерживается, но мы не забыли о нём! Наша команда уже занята его разработкой, он будет доступен в ближайшее время.
Следите за новыми постами по любимым темам
Подпишитесь на интересующие вас теги, чтобы следить за новыми постами и быть в курсе событий.
ООП против функционального программирования: разница и сравнение
Эффективное и успешное программирование лежит в основе большинства вещей, которые можно найти в Интернете. Единственная цель различных методологий — гарантировать, что код будет понятным, управляемым и может работать без каких-либо ошибок.
Двумя наиболее распространенными и важными методологиями программирования являются объектно-ориентированное программирование и функциональное программирование.
Основные выводы
- Объектно-ориентированное программирование (ООП) организует код вокруг объектов и их взаимодействий, в то время как функциональное программирование фокусируется на функциях и неизменности данных.
- ООП способствует инкапсуляции и наследованию, облегчая повторное использование кода и модульность, в то время как функциональное программирование делает упор на чистые функции и сводит к минимуму побочные эффекты.
- Функциональное программирование больше подходит для параллельной обработки и математических вычислений, в то время как ООП легче концептуализировать для моделирования реальных сценариев.
ООП против функционального программирования
Объектно-ориентированное программирование (ООП) — это парадигма программирования, которая делает упор на создание объектов, обладающих свойствами и методами. Он широко используется в современных языках программирования. Функциональное программирование — это еще одна парадигма программирования, которая фокусируется на написании функций, управляющих данными.
ООП означает объектно-ориентированное программирование. Это концептуальный метод программирования, основанный на использовании объектов в качестве ключа.
Основными чертами ООП являются абстракция, наследование, полиморфизми инкапсуляция.
С другой стороны, функциональное программирование — это метод программирования с функциональными факторами, который делает упор на создание и реализацию программ. В основе функционального программирования лежат различные концепции, такие как чистая функция, системы типов, функции высокого порядка, рекурсия, ссылочная прозрачность, строгая и нестрогая оценка.
Сравнительная таблица
Что такое ООП?
ООП — это краткосрочное обозначение объектно-ориентированного программирования. Это парадигма программирования, основанная исключительно на концепции объектов.
ООП может содержать данные в виде свойств и атрибутов, которые также широко известны как поля. Код содержится в виде методов или процедур.
Отличительной особенностью объектов является доступность и возможность изменения, предлагаемые процедурам объектов для полей данных.
ООП разработал программы с объектами и даже может взаимодействовать друг с другом. Языки в ООП разнообразны.
Самые популярные языки ООП основаны на классах, что просто подразумевает, что объекты являются экземплярами класса. Это также становится определяющим фактором для типов.
ООП Собирает объекты, которые программист может идентифицировать и манипулировать ими.
Существуют различные языки программирования, поддерживающие ООП, такие как Java, C++, Python, Visual Basic.NET, MATLAB, Objective-C, SIMSCRIPT, Object Pascal, SmallTalk, Common Lisp и другие. Они формируют мультипарадигму.
Внедрение Simula легло в основу ООП. Этот язык программирования в основном использовался для физического моделирования ООП.
Двумя наиболее совместимыми языками ООП являются Ruby и Python. Оба языка полезны для абстракции данных.
ООП полезен для разработчиков, которые намерены манипулировать объектом и не требуют логики для манипулирования им. Тип программирования больше всего подходит для сложных больших и активно обновляемых программ с регулярным обслуживанием.
Сюда также входят программы для проектирования производства и даже мобильные приложения, работающие под программным обеспечением для моделирования.
Что такое функциональное программирование?
Функциональное программирование — это парадигма программирования, которая создает программы путем составления и применения функций. Тип программирования является декларативным, когда определения функций представляют собой деревья выражений и не содержат последовательности императивных операторов.
Определения сопоставляют значения с другими значениями. Обращение с функциями в функциональном программировании осуществляется как с гражданами первого класса.
Функциональное программирование имеет программы, написанные в компонующем, декларативном стиле и с комбинацией небольших функций модульным образом. Тип программирования считается синонимом программирования чисто функционального.
Чисто функциональное программирование считается подмножеством функционального программирования. Обработка функций как чистых функций или детерминированных математических функций.
Корни функционального программирования в основном уходят в академические круги, которые развились из лямбда-исчисления. Популярность функционального программирования в истории сравнительно меньше императивного программирования.
Однако в настоящее время функциональное программирование в основном используется в образовании и промышленности. Примеры — Haskell, Common Lisp, Racket, Wolfram Language, F#, Scheme, Erlang, Clojure, OCaml и Elixir.
Различные языки преуспели в определенных областях благодаря функциональному программированию, такому как R в статистике, XQuery Или XSLT для XML, JavaScript в Интернете, J, K и Q в финансовом анализе и другие. Другие декларативные языки предметной области часто используют несколько элементов функционального программирования.
Некоторые языки реализуют функции функционального программирования или поддерживают программирование в функциональном стиле. Пример — C++11, PHP, Java (поскольку Java 8), Kotlin, Go, Perl, Rust, Scala, C#, Raku и другие.
Основные различия между ООП и функциональным программированием
- Итерация в ООП выполняется с использованием циклов, а итерация в функциональном программировании выполняется с использованием рекурсии.
- Фундаментальными элементами ООП являются методы и объекты, а фундаментальными элементами функционального программирования являются переменные и функции.
- Данные в ООП находятся в изменяемой форме, в то время как данные в функциональном программировании находятся в изменяемой форме.
- Модель программирования ООП — это модель императивного программирования, а модель программирования функционального программирования — модель декларативного программирования.
- В ООП предусмотрены три спецификатора доступа: Private, Public и Protected, в то время как функциональное программирование не предусматривает ни одного спецификатора доступа.
Рекомендации
- http://www.eecs.ucf.edu/~leavens/ComS541-Schmidt/general-information/reserve-list.ps.gz
- https://projecteuclid.org/journals/statistical-science/volume-29/issue-2/Object-Oriented-Programming-Functional-Programming-and-R/10.1214/13-STS452.short
Один запрос?
Я приложил столько усилий, чтобы написать этот пост в блоге, чтобы предоставить вам ценность. Это будет очень полезно для меня, если вы подумаете о том, чтобы поделиться им в социальных сетях или со своими друзьями/родными. ДЕЛИТЬСЯ ♥️