На странице 839 второго издания Стив Макконнелл обсуждает все способы, с помощью которых программисты могут «преодолевать сложности» в больших программах. Его советы завершаются этим утверждением:
«Объектно-ориентированное программирование обеспечивает уровень абстракции, который применяется одновременно к алгоритмам и данным , своего рода абстракцию, которую не обеспечивала только функциональная декомпозиция».
В сочетании с его выводом о том, что «снижение сложности является, пожалуй, самым важным ключом к тому, чтобы быть эффективным программистом» (на той же странице), это кажется довольно сложной задачей для функционального программирования.
Дебаты между FP и OO часто устраиваются сторонниками FP по вопросам сложности, которые вытекают именно из задач параллелизма или распараллеливания. Но параллелизм - это, конечно, не единственный вид сложности, который нужно решать программистам. Возможно, сосредоточение внимания на уменьшении одного вида сложности значительно увеличивает его в других измерениях, так что во многих случаях выигрыш не стоит затрат.
Если бы мы сместили условия сравнения между FP и OO от конкретных вопросов, таких как параллелизм или повторное использование, к управлению глобальной сложностью, как бы выглядела эта дискуссия?
РЕДАКТИРОВАТЬ
Контраст, который я хотел подчеркнуть, заключается в том, что ОО, кажется, инкапсулирует и абстрагируется от сложности как данных, так и алгоритмов, тогда как функциональное программирование, кажется, поощряет, оставляя детали реализации структур данных более «открытыми» во всей программе.
Смотрите, например, Стюарт Халлоуэй (а Clojure FP поборник) здесь говорят , что «более-спецификация типов данных» является «негативным последствием идиоматического стиля ОО» и в пользу концептуализации в AddressBook как простой вектор или карты вместо богатого объекта OO с дополнительными (не векторными и не подобными карте) свойствами и методами. (Кроме того, сторонники OO и Domain-Driven Design могут сказать, что раскрытие адресной книги в виде вектора или карты приводит к чрезмерной экспозиции инкапсулированных данных с методами, которые не имеют значения или даже опасны с точки зрения домена).
Ответы:
Имейте в виду, что книга была написана более 20 лет назад. Для профессиональных программистов того времени FP не существовало - оно целиком принадлежало ученым и исследователям.
Нам нужно сформулировать «функциональную декомпозицию» в соответствующем контексте работы. Автор не имеет в виду функциональное программирование. Мы должны связать это со «структурированным программированием» и
GOTO
заполненным беспорядком, который был до этого. Если ваша точка отсчета старая FORTRAN / COBOL / BASIC, в которой не было функций (возможно, если вам повезет, вы получите один уровень GOSUB), и все ваши переменные являются глобальными, то есть могут разбить вашу программу на слои функций является главным благом.ООП является дальнейшим уточнением такого рода «функциональной декомпозиции». Вы можете не только объединять инструкции в функции, но и группировать связанные функции с данными, над которыми они работают. Результатом является четко определенный фрагмент кода, который вы можете посмотреть и понять (в идеале), не прибегая к поиску кода, чтобы выяснить, что еще может повлиять на ваши данные.
источник
Я полагаю, что сторонники функционального программирования утверждают, что большинство языков FP предоставляют больше средств абстракции, чем «только функциональная декомпозиция», и фактически позволяют средства абстракций, сопоставимые по мощности с объектно-ориентированными языками. Например, можно привести классы типов Хаскелла или модули высшего порядка ML как такие средства абстракций. Таким образом, утверждение (которое, я уверен, касалось объектной ориентации или процедурного программирования, а не функционального программирования) к ним не относится.
Следует также отметить, что FP и OOP являются ортогональными понятиями и не являются взаимоисключающими. Поэтому не имеет смысла сравнивать их друг с другом. Вы можете очень хорошо сравнить «императивный ООП» (например, Java) с «функциональным ООП» (например, Scala), но приведенное вами утверждение не будет применяться к этому сравнению.
источник
Я считаю, что функциональное программирование чрезвычайно полезно при управлении сложностью. Однако вы склонны думать о сложности по-другому, определяя ее как функции, которые действуют на неизменяемые данные на разных уровнях, а не на инкапсуляцию в смысле ООП.
Например, я недавно написал игру на Clojure, и все состояние игры было определено в единой неизменной структуре данных:
И основной игровой цикл можно определить как применение некоторых чистых функций к игровому состоянию в цикле:
Ключевая функция называется
update-game
, которая выполняет этап моделирования с учетом предыдущего игрового состояния и некоторого пользовательского ввода и возвращает новое игровое состояние.Так в чем же сложность? На мой взгляд, это было сделано довольно хорошо:
ООП также может управлять сложностью посредством инкапсуляции, но если сравнить это с ООП, у функционала есть несколько очень больших преимуществ:
Наконец, для людей, которые заинтересованы в более глубоком понимании того, как управлять сложностью в функциональных и ООП-языках, я настоятельно рекомендую видеоролик с основной речью Рича Хикки « Simple Made Easy» (снятый на технологической конференции Strange Loop ).
источник
Одной только функциональной декомпозиции недостаточно для создания какого-либо алгоритма или программы: вам также необходимо представлять данные. Я думаю, что приведенное выше утверждение неявно предполагает (или, по крайней мере, его можно понять так), что «данные» в функциональном случае имеют самый элементарный вид: просто списки символов и ничего больше. Программирование на таком языке, очевидно, не очень удобно. Однако многие, особенно новые и современные, функциональные (или мультипарадигмальные) языки, такие как Clojure, предлагают богатые структуры данных: не только списки, но и строки, векторы, карты и наборы, записи, структуры - и объекты! - с метаданными и полиморфизмом.
Огромный практический успех ОО абстракций вряд ли можно оспорить. Но это последнее слово? Как вы писали, проблемы параллелизма уже являются основной болью, и классический ОО вообще не содержит представления о параллелизме. В результате де-факто ОО-решения для работы с параллелизмом представляют собой просто наложенную клейкую ленту: работает, но легко облажается, отнимает значительное количество мозговых ресурсов от основной задачи и плохо масштабируется. Может быть, можно взять лучшее из многих миров. Вот что преследуют современные мультипарадигмальные языки.
источник
Изменчивое состояние является корнем большинства сложностей и проблем, связанных с программированием и программным / системным проектированием.
ОО охватывает изменяемое состояние. FP не терпит изменчивого состояния.
И у ОО, и у ФП есть свои применения и приятные места. Выбирать мудро. И помните пословицу: «Закрытия - это объекты бедняков. Объекты - это замыкания бедняков».
источник
Функциональное программирование может иметь объекты, но эти объекты имеют тенденцию быть неизменными. Чистые функции (функции без побочных эффектов) затем работают с этими структурами данных. Можно создавать неизменяемые объекты на объектно-ориентированных языках программирования, но они не были предназначены для этого, и это не то, как они обычно используются. Это затрудняет рассуждение об объектно-ориентированных программах.
Давайте рассмотрим очень простой пример. Допустим, Oracle решил, что Java Strings должен иметь обратный метод, и вы написали следующий код.
что оценивает последняя строка? Вам нужны специальные знания о классе String, чтобы знать, что это будет иметь значение false.
Что делать, если я сделал свой собственный класс WuHoString
Невозможно знать, к чему относится последняя строка.
В стиле функционального программирования это будет написано так:
и это должно быть правдой.
Если одну функцию в одном из самых базовых классов так сложно рассуждать, то возникает вопрос: увеличилась или уменьшилась сложность представления об изменчивых объектах?
Очевидно, что есть все виды определений того, что представляет собой объектно-ориентированное и что значит быть функциональным и что значит иметь и то, и другое. Для меня вы можете иметь «стиль функционального программирования» на языках, в которых нет таких вещей, как функции первого класса, но для него созданы другие языки.
источник
Я думаю, что в большинстве случаев классическая абстракция ООП не покрывает сложности параллелизма. Поэтому ООП (в своем первоначальном значении) не исключает FP, и поэтому мы видим такие вещи, как scala.
источник
Ответ зависит от языка. Лисп, например, действительно правильно понимает, что код - это данные - алгоритмы, которые вы пишете, на самом деле являются просто списками Лиспа! Вы храните данные так же, как вы пишете программу. Эта абстракция одновременно проще и тщательнее, чем ООП, и позволяет вам делать действительно полезные вещи (посмотрите макросы).
У Haskell (и похожего языка, я думаю) есть совершенно другой ответ: алгебраические типы данных. Алгебраический тип данных похож на
C
структуру, но с большим количеством опций. Эти типы данных обеспечивают абстракцию, необходимую для моделирования данных; функции обеспечивают абстракцию, необходимую для моделирования алгоритмов. Классы типов и другие расширенные возможности обеспечивают еще более высокий уровень абстракции над обоими.Например, я работаю над языком программирования под названием TPL для удовольствия. Алгебраические типы данных позволяют действительно легко представлять значения:
Это очень наглядно говорит о том, что TPLValue (любое значение на моем языке) может быть
Null
илиNumber
соInteger
значением или дажеFunction
со списком значений (параметров) и конечным значением (телом). ).Затем я могу использовать классы типов для кодирования некоторых общих действий. Например, я мог бы сделать
TPLValue
и экземпляр,Show
который означает, что он может быть преобразован в строку.Кроме того, я могу использовать свои собственные классы типов, когда мне нужно указать поведение определенных типов (включая те, которые я сам не реализовал). Например, у меня есть
Extractable
класс типов, который позволяет мне написать функцию, которая принимаетTPLValue
и возвращает соответствующее нормальное значение. Таким образом,extract
можно преобразовать aNumber
в aInteger
или aString
в a,String
еслиInteger
иString
являются экземплярамиExtractable
.Наконец, основная логика моей программы заключается в нескольких функциях, таких как
eval
иapply
. Это действительно ядро - они берутTPLValue
s и превращают их в большеTPLValue
s, а также обрабатывают состояние и ошибки.В целом, абстракции, которые я использую в своем коде на Haskell, на самом деле более мощные, чем те, которые я использовал бы в языке ООП.
источник
eval
. «Эй, посмотри на меня! Мне не нужно писать собственные дыры в безопасности; у меня есть уязвимость, связанная с выполнением произвольного кода, встроенная прямо в язык программирования!» Сопоставление данных с кодом является основной причиной одного из двух самых популярных классов уязвимостей безопасности всех времен. Каждый раз, когда вы видите, что кто-то взломан из-за атаки с использованием SQL-инъекции (помимо всего прочего), это потому, что некоторые программисты не знают, как правильно отделить данные от кода.eval
не сильно зависит от структуры Lisp - вы можете использовать такиеeval
языки, как JavaScript и Python. Настоящая сила заключается в написании макросов, которые в основном являются программами, которые работают с такими программами, как данные, и выводят другие программы. Это делает язык очень гибким и легко создает мощные абстракции.and
. короткое замыканиеor
.let
,let-rec
,cond
,defn
, Ничто из этого не может быть реализовано с помощью функций на языках соответствующего порядка.for
(список пониманий).dotimes
,doto
,and
!» Я слышу: «Эй, посмотри на меня, мой язык настолько искажен, что даже с коротким замыканием не приходит,and
и мне нужно заново изобретать колесо для всего !»Насколько я вижу, цитируемое предложение больше не имеет никакой юридической силы.
Современные ОО-языки не могут абстрагироваться от типов, тип которых не *, то есть типы с более высоким родом неизвестны. Их система типов не позволяет выразить идею «некоторого контейнера с элементами Int, который позволяет отобразить функцию над элементами».
Следовательно, эта основная функция, как Haskells
не может быть легко написано на Java *), например, по крайней мере, не безопасным способом. Следовательно, чтобы получить базовую функциональность, вы должны написать множество шаблонов, потому что вам нужно
И, тем не менее, эти пять методов в основном представляют собой один и тот же код, один или несколько. Напротив, в Хаскеле мне понадобится:
Обратите внимание, что это не изменится в Java 8 (просто можно проще применять функции, но тогда точно возникнет описанная выше проблема. Пока у вас даже нет функций более высокого порядка, вы, скорее всего, даже не в состоянии понять, для чего хороши более высокие типы.)
Даже новые ОО-языки, такие как Цейлон, не имеют более высокодобренных типов. (В последнее время я спрашивал Гэвина Кинга, и он сказал мне, что это сейчас не важно.) Не знаю, как насчет Котлина.
*) Если честно, у вас может быть интерфейс Functor с методом fmap. Плохо то, что вы не можете сказать: эй, я знаю, как реализовать fmap для библиотечного класса SuperConcurrentBlockedDoublyLinkedDequeHasMap, дорогой компилятор, пожалуйста, примите, что с этого момента все SuperConcurrentBlockedDoublyLinkedDequeHasMaps являются функторами.
источник
Любой, кто когда-либо программировал в dBase, знал, насколько полезны однострочные макросы для создания кода многократного использования. Хотя я не программировал на Лиспе, я читал от многих других, которые клянутся макросами времени компиляции. Идея внедрения кода в ваш код во время компиляции используется в простой форме в каждой C-программе с директивой include. Поскольку Лисп может сделать это с помощью программы на Лиспе, а Лисп обладает высокой отражающей способностью, вы получаете гораздо более гибкие включения.
Любой программист, который просто взял бы произвольную текстовую строку из Интернета и передал ее в свою базу данных, не является программистом. Точно так же любой, кто позволил бы «пользовательским» данным автоматически становиться исполняемым кодом, явно глуп. Это не означает, что позволить программам манипулировать данными во время выполнения, а затем выполнять их как код - плохая идея. Я полагаю, что эта техника будет незаменимой в будущем, когда в большинстве программ будет «умный» код. Вся «проблема с данными / кодом» или нет - это вопрос безопасности на языке.
Одна из проблем большинства языков заключается в том, что они созданы для того, чтобы один человек в автономном режиме мог выполнять некоторые функции для себя. Реальные программы требуют, чтобы у многих людей был доступ в любое время и одновременно с нескольких ядер и нескольких компьютерных кластеров. Безопасность должна быть частью языка, а не операционной системы, и в недалеком будущем это будет.
источник