В этой статье делается попытка изложить различные сходства и различия между различными парадигмами программирования в виде резюме как в графическом, так и в табличном формате со ссылками на отдельные обсуждения этих сходств и различий в существующих статьях Википедии.
Основные парадигмальные подходы
Есть два основных подхода к программированию:
- Императивное программирование - фокусы о том , как выполнить, определяет управление потоком , как заявления , которые изменяют программу состояния .
- Декларативное программирование - фокусируется на том, что выполнять, определяет логику программы, но не подробный поток управления .
Следующие примеры широко считаются основными парадигмами программирования, как это видно при измерении популярности языков программирования :
- Процедурное программирование , структурированное программирование - определяет шаги, которые программа должна предпринять, чтобы достичь желаемого состояния.
- Функциональное программирование - рассматривает программы как оценивающие математические функции и избегает состояния и изменяемых данных.
- Объектно-ориентированное программирование (ООП) - организует программы как объекты : структуры данных, состоящие из полей данных и методов вместе с их взаимодействиями.
Ниже приведены распространенные типы программирования, которые могут быть реализованы с использованием различных парадигм:
- Программирование, управляемое событиями - поток управления программой определяется событиями , такими как входные данные датчиков или действия пользователя ( щелчки мыши, нажатия клавиш) или сообщения от других программ или потоков .
- Программирование на основе автоматов - программа или часть рассматривается как модель конечного автомата или любого другого формального автомата.
- Реактивное программирование - это парадигма декларативного программирования, связанная с потоками данных и распространением изменений.
Подпрограммы, реализующие методы ООП, могут быть в конечном итоге закодированы в императивном, функциональном или процедурном стиле, который может или не может напрямую изменять состояние от имени вызывающей программы. Между парадигмами неизбежно есть некоторое совпадение, но основные особенности или идентифицируемые различия суммированы в этой таблице:
Парадигма | Описание | Основные черты | Связанные парадигмы | Критика | Примеры |
---|---|---|---|---|---|
Императив | Программы как операторы, которые напрямую изменяют вычисленное состояние ( поля данных ) | Прямые назначения , общие структуры данных , глобальные переменные | Эдсгер В. Дейкстра , Майкл А. Джексон | C , C ++ , Java , Kotlin , PHP , Python , Ruby | |
Структурированный | Стиль императивного программирования с более логичной структурой программы | Structograms , отступы , отсутствие или ограниченное использование GOTO заявления | Императив | C , C ++ , Java , Kotlin , Паскаль , PHP , Python | |
Процедурный | На основе структурного программирования, основанного на концепции модульного программирования или вызова процедуры. | Локальные переменные , последовательность, выбор, итерация и модуляризация | Структурированный, императивный | C , C ++ , Lisp , PHP , Python | |
Функциональный | Рассматривает вычисления как оценку математических функций, избегая состояния и изменяемых данных. | Лямбда-исчисление , композиционность , формула , рекурсия , ссылочная прозрачность , без побочных эффектов | Декларативная | C ++ , [1] C # , [2] [ круговая ссылка ] Clojure , Coffeescript , [3] Elixir , Erlang , F # , Haskell , Java (начиная с версии 8), Kotlin , Lisp , Python , R , [4] Ruby , Scala , SequenceL , стандартный машинный перевод , JavaScript , вяз | |
Событийный, в том числе временной | Поток управления определяется в основном событиями , такими как щелчки мыши или прерывания, включая таймер. | Основной цикл , обработчики событий, асинхронные процессы | Процедурные, информационный поток | JavaScript , ActionScript , Visual Basic , Elm | |
Объектно-ориентированный | Лечит поля данных в качестве объектов манипулируют с помощью предопределенных методов только | Объекты , методы, передача сообщений , скрытие информации , абстракция данных , инкапсуляция , полиморфизм , наследование , сериализация -маршаллинг | Процедурный | Википедия и другие [5] [6] [7] | Common Lisp , C ++ , C # , Eiffel , Java , Kotlin , PHP , Python , Ruby , Scala , JavaScript [8] [9] |
Декларативная | Определяет логику программы, но не подробный поток управления | Языки четвертого поколения , электронные таблицы , генераторы программ отчетов | SQL , регулярные выражения , Prolog , OWL , SPARQL , Datalog , XSLT | ||
Автоматное программирование | Рассматривает программы как модель конечного автомата или любого другого формального автомата. | Государственное перечисление , управляющая переменная , состояние изменяется, изоморфизм , таблица состояний перехода | Императивный, событийный | Абстрактный язык конечных автоматов |
Различия в терминологии
Несмотря на то, что несколько (типов) парадигм программирования существуют параллельно (с иногда явно противоречивыми определениями), многие из базовых фундаментальных компонентов остаются более или менее одинаковыми ( константы , переменные , поля данных , подпрограммы , вызовы и т. Д.) И каким-то образом неизбежно должны быть включены в каждую отдельную парадигму с одинаково похожими атрибутами или функциями. Приведенная выше таблица не предназначена для определения точных сходств, а скорее как указатель того, где искать дополнительную информацию, основанную на различных именах этих сущностей в каждой парадигме. Еще более усложняют ситуацию нестандартизированные реализации каждой парадигмы на многих языках программирования , особенно на языках, поддерживающих несколько парадигм , каждая со своим собственным жаргоном .
Языковая поддержка
Синтаксический сахар - это улучшение функциональности программы за счет введения языковых функций, облегчающих конкретное использование, даже если конечный результат может быть достигнут без них. Одним из примеров синтаксического сахара могут быть классы, используемые в объектно-ориентированных языках программирования . Императивный язык C может поддерживать объектно-ориентированное программирование с помощью своих средств указателей на функции , приведения типов и структур. Однако такие языки, как C ++, стремятся сделать объектно-ориентированное программирование более удобным путем введения синтаксиса, специфичного для этого стиля кодирования. Более того, специализированный синтаксис подчеркивает объектно-ориентированный подход. Точно так же функции и синтаксис циклов в C (и других процедурных и структурированных языках программирования) можно рассматривать как синтаксический сахар. Язык ассемблера может поддерживать процедурное или структурированное программирование с помощью своих средств изменения значений регистров и выполнения ветвления в зависимости от состояния программы. Однако в таких языках, как C, введен синтаксис, специфичный для этих стилей кодирования, чтобы сделать процедурное и структурированное программирование более удобным. Возможности языка C # (C Sharp), такие как свойства и интерфейсы, аналогичным образом не позволяют использовать новые функции, но предназначены для того, чтобы сделать хорошие методы программирования более заметными и естественными.
Некоторые программисты считают эти возможности несущественными или даже несерьезными. Например, Алан Перлис однажды пошутил, ссылаясь на языки , разделенные скобками , что «синтаксический сахар вызывает рак точки с запятой » (см. « Эпиграммы по программированию» ).
Расширением этого является синтаксический сахарин или бесплатный синтаксис, который не упрощает программирование. [10]
Сравнение производительности
Только при общей длине пути инструкций программа, написанная в императивном стиле, не использующая подпрограмм, будет иметь наименьшее количество. Однако двоичный размер такой программы может быть больше, чем у той же программы, закодированной с использованием подпрограмм (как в функциональном и процедурном программировании), и будет ссылаться на большее количество нелокальных физических инструкций, которые могут увеличивать количество промахов в кэше и накладные расходы на выборку инструкций в современных процессорах .
Парадигмы, которые широко используют подпрограммы (включая функциональные, процедурные и объектно-ориентированные) и не используют также значительного встроенного расширения (встраивание через оптимизацию компилятора ), следовательно, будут использовать большую часть общих ресурсов на связях подпрограмм. Объектно-ориентированные программы, которые намеренно не изменяют состояние программы напрямую, а вместо этого используют методы мутатора (или сеттеры ) для инкапсуляции этих изменений состояния, как прямое следствие, будут иметь больше накладных расходов. Это связано с тем, что передача сообщений - это, по сути, вызов подпрограммы, но с тремя дополнительными накладными расходами: динамическое выделение памяти , копирование параметров и динамическая отправка . Получение памяти из кучи и копирование параметров для передачи сообщений может потребовать значительных ресурсов, которые намного превышают ресурсы, необходимые для изменения состояния. Аксессоры (или геттеры ), которые просто возвращают значения частных переменных-членов, также зависят от аналогичных подпрограмм передачи сообщений вместо использования более прямого присваивания (или сравнения), увеличивающего общую длину пути.
Управляемый код
Для программ, выполняемых в среде управляемого кода , такой как .NET Framework , многие проблемы влияют на производительность, на которые существенно влияет парадигма языка программирования и различные используемые языковые функции. [11]
Примеры псевдокода, сравнивающие различные парадигмы
Псевдокод сравнение императива, процедурный и объектно - ориентированных подходы , используемые для вычисления площади круга (πr²), предполагая отсутствие подпрограммы встраивания , нет макро препроцессоров , зарегистрировать арифметические и взвешивание каждый «шага» команд , как только одна инструкция - как грубая мера длины пути команд - представлена ниже. Шаг инструкции, который концептуально выполняет изменение состояния, в каждом случае выделяется жирным шрифтом. Арифметические операции, используемые для вычисления площади круга, одинаковы во всех трех парадигмах, с той разницей, что процедурная и объектно-ориентированная парадигмы заключают эти операции в вызов подпрограммы, что делает вычисление универсальным и многократно используемым. Тот же эффект может быть достигнут в чисто императивной программе с использованием препроцессора макросов только за счет увеличения размера программы (только на каждом сайте вызова макроса) без соответствующей пропорциональной стоимости времени выполнения (пропорциональной n вызовам - которые могут быть расположены в пределах внутренний цикл, например). И наоборот, встраивание подпрограмм компилятором может уменьшить процедурные программы до чего-то похожего по размеру на чисто императивный код. Однако для объектно-ориентированных программ, даже со встраиванием, сообщения все равно должны быть построены (из копий аргументов) для обработки объектно-ориентированными методами. Накладные расходы на вызовы, виртуальные или иные, не зависят от изменения потока управления, а зависят от затрат, связанных с соглашением о вызовах , такими как код пролога и эпилога , настройка стека и передача аргументов [12] (см. Здесь [13] для более реалистичных инструкций длина пути, стек и другие затраты, связанные с вызовами на платформе x86 ). См. Также здесь [14] слайд-презентацию Эрика С. Робертса («Выделение памяти для переменных», глава 7) [15], где показано использование стека и памяти кучи при суммировании трех рациональных чисел в объекте Java. -ориентированный язык.
Императив | Процедурный | Объектно-ориентированный |
---|---|---|
нагрузка r; 1 r2 = r * r; 2 результат = r2 * "3,142"; 3...................... место хранения .............переменная результатаконстанта "3,142" | область proc (r2, res): толкать стек 5 нагрузка r2; 6 r3 = r2 * r2; 7 res = r3 * "3,142"; 8 поп стек 9 возвращаться; 10...............................................основной процесс: нагрузка r; 1 зона звонка (r, результат); + загрузка p = адрес списка параметров; 2 + загрузка v = адрес подпрограммы 'area'; 3 + goto v с возвратом; 4........ место хранения .............переменная результатаконстанта "3,142"переменная списка параметровуказатель на функцию (==> область)стековое хранилище | circle.area метод (r2): толкать стек 7 нагрузка r2; 8 r3 = r2 * r2; 9 res = r3 * "3,142"; 10 поп стек 11 return (res); 12,13...............................................основной процесс: нагрузка r; 1 результат = circle.area (r); + выделить память в куче; 2 [См. 1] + скопировать r в сообщение; 3 + загрузка p = адрес сообщения; 4 + загрузить v = адрес. метода 'circle.area' 5 + goto v с возвратом; 6...... место хранения .............переменная результата (предполагается, что она выделена заранее)неизменяемая переменная "3,142" (окончательная)(куча) переменная сообщения для вызова метода кругаvtable (==> площадь)стековое хранилище |
- ^ См. Раздел: Выделение динамической памяти для хранения сообщений и объектов.
Преимущества процедурной абстракции и объектно-ориентированного полиморфизма плохо иллюстрируются небольшим примером, подобным приведенному выше. Этот пример предназначен в основном для иллюстрации некоторых внутренних различий в производительности, а не для абстракции или повторного использования кода.
Подпрограмма, накладные расходы на вызов метода
Присутствие (вызываемой) подпрограммы в программе не вносит ничего лишнего в функциональность программы независимо от парадигмы, но может в значительной степени способствовать структурированию и универсальности программы, что значительно упрощает ее написание, изменение и расширение. [16] Степень, в которой различные парадигмы используют подпрограммы (и связанные с ними требования к памяти), влияет на общую производительность всего алгоритма, хотя, как указал Гай Стил в статье 1977 года, хорошо спроектированная реализация языка программирования может иметь очень низкие накладные расходы. для процедурной абстракции (но сожалеет, что в большинстве реализаций они редко достигают этого на практике - будучи «довольно легкомысленными или небрежными в этом отношении»). В той же статье Стил также рассматривает аргументы в пользу программирования, основанного на автоматах (с использованием вызовов процедур с хвостовой рекурсией ), и приходит к выводу, что «мы должны проявлять здоровое уважение к вызовам процедур» (поскольку они мощные), но предложил «использовать их экономно " [16]
По частоте вызовов подпрограмм:
- Для процедурного программирования степень детализации кода в значительной степени определяется количеством дискретных процедур или модулей .
- Для функционального программирования частые вызовы библиотечных подпрограмм являются обычным явлением [ необходима цитата ], но часто могут быть встроены оптимизирующим компилятором.
- Для объектно-ориентированного программирования количество вызываемых вызовов методов также частично определяется степенью детализации структур данных и, таким образом, может включать в себя множество доступов только для чтения к объектам низкого уровня, которые инкапсулированы и, следовательно, недоступны ни в каком другом, более прямом виде, способ. Поскольку повышенная степень детализации является предпосылкой для большего повторного использования кода , наблюдается тенденция к детализации структур данных и соответствующему увеличению количества дискретных объектов (и их методов) и, следовательно, вызовов подпрограмм. Активно не приветствуется создание божественных объектов . Конструкторы также добавляют к счетчику, поскольку они также являются вызовами подпрограмм (если они не встроены). Проблемы с производительностью, вызванные чрезмерной детализацией, могут не проявиться до тех пор, пока масштабируемость не станет проблемой.
- Для других парадигм, где может использоваться сочетание вышеперечисленных парадигм, использование подпрограмм менее предсказуемо.
Выделение динамической памяти для хранения сообщений и объектов
Уникально объектно-ориентированная парадигма включает динамическое выделение памяти из кучи как для создания объектов, так и для передачи сообщений. Тест 1994 года - «Затраты на выделение памяти в больших программах на C и C ++», проведенный Digital Equipment Corporation для разнообразного программного обеспечения с использованием инструмента профилирования на уровне инструкций, позволил измерить, сколько инструкций требовалось для распределения динамической памяти. Результаты показали, что наименьшее абсолютное количество выполненных инструкций составляло в среднем около 50, но другие достигали 611. [17] См. Также «Куча: Удовольствия и боли» Мурали Р. Кришнана [18], в котором говорится: «Реализации кучи, как правило, остаются общие для всех платформ и, следовательно, имеют большие накладные расходы ". В статье IBM 1996 года «Масштабируемость алгоритмов динамического распределения памяти» Аруна Айенгара из IBM [19] демонстрируются различные алгоритмы динамической памяти и их соответствующее количество команд. Даже рекомендуемый алгоритм MFLF I (HS Stone, RC 9674) показывает количество инструкций в диапазоне от 200 до 400. Приведенный выше пример псевдокода не включает реалистичную оценку этой длины пути выделения памяти или задействованных накладных расходов префикса памяти и связанного с этим мусора. накладные расходы по сбору. Настоятельно заявляя, что выделение кучи является нетривиальной задачей, один микрокаллокатор программного обеспечения с открытым исходным кодом , разработанный разработчиком игр Джоном В. Рэтклиффом , состоит из почти 1000 строк кода. [20]
Динамически отправляемые вызовы сообщений v. Накладные расходы на прямые вызовы процедур
В своей аннотации « Оптимизация объектно-ориентированных программ с использованием анализа иерархии статических классов » [21] Джеффри Дин, Дэвид Гроув и Крейг Чемберс с факультета компьютерных наук и инженерии Вашингтонского университета утверждают, что «интенсивное использование наследование и динамически привязанные сообщения, вероятно, сделают код более расширяемым и повторно используемым, но это также накладывает значительные накладные расходы на производительность по сравнению с эквивалентной, но нерасширяемой программой, написанной не объектно-ориентированным способом. В некоторых областях, таких как Для пакетов структурированной графики стоимость дополнительной гибкости, обеспечиваемой за счет использования сильно объектно-ориентированного стиля, является приемлемой. Однако в других областях, таких как библиотеки базовых структур данных, пакеты числовых вычислений, библиотеки рендеринга и фреймворки моделирования на основе трассировки, стоимость передачи сообщений может быть слишком высокой, что вынуждает программиста избегать объектно-ориентированного программирования в «горячих точках» своего приложения ».
Сериализация объектов
Сериализация накладывает большие накладные расходы при передаче объектов из одной системы в другую, особенно когда передача осуществляется в удобочитаемых форматах, таких как Extensible Markup Language ( XML ) и JavaScript Object Notation ( JSON ). Это контрастирует с компактными двоичными форматами для не объектно-ориентированных данных. И кодирование, и декодирование значения данных объекта и его атрибутов участвуют в процессе сериализации, который также включает понимание сложных проблем, таких как наследование, инкапсуляция и скрытие данных.
Параллельные вычисления
Профессор Университета Карнеги-Меллона Роберт Харпер в марте 2011 года написал: «В этом семестре мы с Дэном Ликатой совместно преподаем новый курс функционального программирования для будущих студентов первого года обучения CS ... Объектно-ориентированное программирование полностью исключено из вводной программы. , потому что он одновременно антимодульный и антипараллельный по самой своей природе и, следовательно, не подходит для современной учебной программы CS. Предлагаемый новый курс по методологии объектно-ориентированного проектирования будет предложен на втором курсе для тех студентов, которые хотят изучать Эта тема." [22]
Смотрите также
- Сравнение языков программирования
- Сравнение языков программирования (базовые инструкции)
- Гранулярность # вычисления
- Передача сообщений
- Подпрограмма
Рекомендации
- ^ «Архивная копия» (PDF) . Архивировано из оригинального (PDF) 02.02.2017 . Проверено 18 декабря 2015 .CS1 maint: заархивированная копия как заголовок ( ссылка )
- ^ «Функциональное программирование C #» . Август 2020 . Проверено 14 августа 2015 .
- ^ Руис, Седрик (май 2014 г.). «Функциональный CoffeeScript для нетерпеливых» . Блог де Седрика Руиса . Седрик Руис . Проверено 9 августа 2015 .
- ^ http://adv-r.had.co.nz/Functional-programming.html
- ^ Шелли, Асаф (22 августа 2008 г.). «Недостатки объектно-ориентированного моделирования» . Сеть программного обеспечения Intel . Проверено 4 июля 2010 .
- ^ Егге, Стив (30 марта 2006 г.). «Казнь в царстве существительных» . steve-yegge.blogspot.com . Проверено 3 июля 2010 .
- ^ [1]
- ^ Крокфорд, Дуглас. «JavaScript: самый непонятый язык программирования в мире» . crockford.com.
- ^ Крокфорд, Дуглас. «Частные члены в JavaScript» . crockford.com.
- ^ «Файл жаргона v4.4.7:« синтаксический сахар » » .
- ^ Грей, янв (июнь 2003 г.). «Написание более быстрого управляемого кода: знайте, чего стоит» . MSDN . Microsoft.
- ^ «Истинная стоимость звонков» . wordpress.com. 2008-12-30.
- ^ http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames
- ^ Робертс, Эрик С. (2008). «Искусство и наука Java; Глава 7: Объекты и память» . Стэндфордский Университет. Архивировано из оригинала 2011-06-06 . Проверено 17 мая 2010 .
- ^ Робертс, Эрик С. (2008). Искусство и наука Явы . Эддисон-Уэсли. ISBN 978-0-321-48612-7. Архивировано из оригинала 2011-06-06 . Проверено 17 мая 2010 .
- ^ a b Гай Льюис Стил-младший «Разоблачение мифа о« дорогостоящем вызове процедур »или реализациях вызовов процедур, признанных вредными, или Lambda: The Ultimate GOTO». MIT AI Lab. Памятка лаборатории искусственного интеллекта AIM-443. Октябрь 1977 г. [2] Архивировано 29 декабря 2009 г. в Wayback Machine [3] [4]
- ^ Детлефс, Дэвид; Dosser, Al; Цорн, Бенджамин (июнь 1994). «Затраты на выделение памяти в больших программах на C и C ++; стр. 532». Программное обеспечение - практика и опыт . 24 (6): 527–542. CiteSeerX 10.1.1.30.3073 . DOI : 10.1002 / spe.4380240602 .
- ^ Кришнан, Мурали Р. (февраль 1999 г.). «Куча: радости и боли» . microsoft.com.
- ^ «Масштабируемость алгоритмов динамического распределения памяти». CiteSeerX 10.1.1.3.3759 . Цитировать журнал требует
|journal=
( помощь ) - ^ "MicroAllocator.h" . Код Google . Проверено 29 января 2012 .
- ^ Дин, Джеффри; Гроув, Дэвид; Чемберс, Крейг (1995). «Оптимизация объектно-ориентированных программ с использованием анализа иерархии статических классов». Объектно-ориентированное программирование . Конспект лекций по информатике. 952 . Вашингтонский университет. С. 77–101. CiteSeerX 10.1.1.117.2420 . DOI : 10.1007 / 3-540-49538-X_5 . ISBN 978-3-540-60160-9.
- ^ Обучение FP для первокурсников , из блога Харпера о вводном обучении информатике. [5]
дальнейшее чтение
- "Распределитель памяти" Дуга Ли
- «Распределение динамической памяти и связанные структуры данных» ( Шотландский квалификационный орган )
- "Внутри распределителя памяти" доктор-новичок, доктор философии.
Внешние ссылки
- Сравнение парадигм программирования от д - ра Рейчел Харрисон и г - н Линси Samaraweera
- Сравнение программирования парадигм: оценка функционального и объектно-ориентированных программ по Харрисон, Р. , Samaraweera, LG, Доби, MR и Льюис, PH (1996) С. 247-254.. ISSN 0268-6961
- «Основные парадигмы программирования» Питер Ван Рой
- "Концепции, методы и модели компьютерного программирования" (2004 г.) Питера Ван Роя и Сейфа Хариди, ISBN 0-262-22069-5
- Истинная стоимость звонков - из блога "Жестче, лучше, быстрее, сильнее" компьютерного ученого Стивена Пиджа.