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

Что означают термины функциональное, декларативное и императивное программирование?

14 ответов

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

См. Также мое объяснение, почему программирование электронных таблиц является декларативным, несмотря на то, что формулы изменяют ячейки.

Кроме того, в нескольких ответах утверждается, что функциональное программирование должно быть подмножеством декларативного. От этого зависит, будем ли мы отличать "функцию" от "процедуры". Давайте сначала обратимся к императиву против декларативного.

Определение декларативного выражения

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

100% декларативный язык (т. Е. Тот, в котором каждое возможное выражение является RT) не (среди других требований RT) не допускает мутации хранимых значений, например HTML и большей части Haskell.

Определение выражения RT

RT часто называют "без побочных эффектов". Термин " эффекты" не имеет точного определения, поэтому некоторые люди не согласны с тем, что "нет побочных эффектов" - это то же самое, что RT. RT имеет точное определение.

Поскольку каждое подвыражение концептуально является вызовом функции, RT требует, чтобы реализация функции (т. Е. Выражение (я) внутри вызываемой функции) не имела доступа к изменяемому состоянию, внешнему по отношению к функции (доступ к изменяемому локальному состоянию позволил). Проще говоря, функция (реализация) должна быть чистой.

Определение чистой функции

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

Чистые функции имеют следующие атрибуты.

  • единственный наблюдаемый результат - возвращаемое значение.
  • единственная выходная зависимость - это аргументы.
  • Аргументы полностью определяются до того, как будет сгенерирован любой вывод

Помните, что RT применяется к выражениям (которые включают вызовы функций), а чистота применяется к (реализациям) функций.

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

Производные атрибуты RT

Любой другой атрибут, процитированный для декларативного программирования, например, цитата из 1999 года, используемая в Википедии, либо происходит от RT, либо используется совместно с императивным программированием. Таким образом, доказывая, что мое точное определение является правильным.

Обратите внимание, что неизменность внешних значений является подмножеством требований к RT.

  • Декларативные языки не имеют циклических структур управления, например for а также while потому что из-за неизменности условие цикла никогда не изменится.

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

  • Декларативные языки выражают логические "шаги" (т. Е. Порядок вызова вложенных функций RT), но не является ли декларативное программирование тем, что каждый вызов функции является семантикой более высокого уровня (т. Е. "Что делать"). Отличие от императива заключается в том, что из-за неизменности (т. Е. В более общем смысле RT) эти "шаги" не могут зависеть от изменяемого состояния, а только от реляционного порядка выраженной логики (т. Е. Порядка вложения вызовов функции, то есть подвыражений).).

    Например, абзац HTML <p> не может быть отображено, пока подвыражения (то есть теги) в абзаце не были оценены. Нет изменяемого состояния, есть только зависимость порядка из-за логической взаимосвязи иерархии тегов (вложенность подвыражений, которые являются аналогично вложенными вызовами функций).

  • Таким образом, существует производный атрибут неизменяемости (в более общем смысле, RT), в котором декларативные выражения выражают только логические отношения составляющих частей (то есть аргументов функции подвыражения), а не отношения изменяемого состояния.

Порядок оценки

Выбор порядка вычисления подвыражений может дать переменный результат только в том случае, если какой-либо из вызовов функции не является RT (т. Е. Функция не является чистой), например, доступ к некоторому изменяемому состоянию, внешнему по отношению к функции, осуществляется внутри функции.

Например, с учетом некоторых вложенных выражений, например, f( g(a, b), h(c, d) ), ленивая и ленивая оценка аргументов функции даст те же результаты, если функции f, g, а также h чисты

Тогда как, если функции f, g, а также h не являются чистыми, то выбор порядка оценки может дать другой результат.

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

Тангенциально, если все идентификаторы, например a, b, c, d, являются неизменяемыми везде, доступ к внешнему по отношению к программе состоянию невозможен (т. е. ввод / вывод), и отсутствует разрыв уровня абстракции, тогда функции всегда чисты.

Кстати, Haskell имеет другой синтаксис, f (g a b) (h c d),

Детали заказа на оценку

Функция - это переход состояния (не изменяемое сохраненное значение) от входа к выходу. Для RT композиций обращений к чистым функциям порядок выполнения этих переходов состояний не зависит. Изменение состояния каждого вызова функции не зависит от других из-за отсутствия побочных эффектов и принципа, что функция RT может быть заменена ее кэшированным значением. Чтобы исправить популярное заблуждение, чистая монадическая композиция всегда декларативна и RT, несмотря на то, что Haskell's IO монада, возможно, нечиста и поэтому обязательна World состояние, внешнее по отношению к программе (но в смысле предостережения ниже, побочные эффекты изолированы).

Стремительная оценка означает, что аргументы функции оцениваются до вызова функции, а ленивая оценка означает, что аргументы не оцениваются до (и если) доступа к ним внутри функции.

Определение: параметры функции объявляются на сайте определения функции, а аргументы функции предоставляются на сайте вызова функции. Знайте разницу между параметром и аргументом.

Концептуально все выражения являются (набором) вызовов функций, например, константы являются функциями без входов, унарные операторы являются функциями с одним входом, двоичные инфиксные операторы являются функциями с двумя входами, конструкторы являются функциями и даже управляющими операторами (например, if, for, while) можно моделировать с помощью функций. Порядок, в котором эти функции аргументов (не путать с порядком вызова вложенных функций) оценивается, не объявляется синтаксисом, например f( g() ) мог охотно оценить g затем f на g или это может оценить f и только лениво оценивать g когда его результат нужен в течение f,

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

Функциональное программирование

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

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

Функциональное программирование обычно делает функцию объектом первого класса, то есть тип функции может появляться в грамматике везде, где может присутствовать любой другой тип. В результате функции могут вводить функции и работать с ними, обеспечивая таким образом разделение задач путем выделения композиции функций, то есть разделения зависимостей между подкомпьютерами детерминированных вычислений.

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

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

параллелизм

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

Принцип Брента: вычисления с работой w и глубиной d могут быть реализованы в p-процессоре PRAM за время O(max(w/p, d)).

И параллелизм, и параллелизм также требуют декларативного программирования, то есть неизменности и RT.

Так откуда взялось это опасное предположение, что параллелизм == параллелизм? Это естественное следствие языков с побочными эффектами: когда у вашего языка есть побочные эффекты повсюду, то каждый раз, когда вы пытаетесь сделать больше, чем одну вещь за раз, вы по существу имеете недетерминизм, вызванный чередованием эффектов от каждой операции, Так что в языках с побочными эффектами единственный способ получить параллелизм - это параллелизм; поэтому неудивительно, что мы часто видим, как они связаны друг с другом.

Порядок оценки FP

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

Стремительный (CBV) и ленивый (CBN) являются категориальными поединками [ 10 ], потому что они имеют обратный порядок оценки, то есть оцениваются ли сначала внешние или внутренние функции соответственно. Представьте себе перевернутое дерево, а затем энергично оценивает от ветви дерева функций до иерархии ветвей к стволу функции верхнего уровня; в то время как ленивый оценивает от ствола до кончиков ветви. У Eager нет конъюнктивных продуктов ("и", a/k/ категориальные "продукты"), а у ленивых нет дизъюнктивных сопутствующих продуктов ("или", a/k/ категориальные "суммы") [ 11 ].

Спектакль

  • нетерпеливый

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

    Эта ненужная работа является причиной заявленного "до" дополнительного логарифмического коэффициента в последовательной временной сложности стремления по сравнению с ленивым, как с чистыми функциями. Решение состоит в том, чтобы использовать функторы (например, списки) с ленивыми конструкторами (т. Е. Стремящимися к необязательным ленивым продуктам), потому что с рвением неверность рвения возникает из внутренней функции. Это связано с тем, что произведения являются конструктивными типами, то есть индуктивными типами с начальной алгеброй на начальной точке фиксации [ 11 ]

  • ленивый

    Как и в случае не прекращения, ленивый слишком ленив с дизъюнктивной функциональной композицией, то есть коиндуктивная окончательность может произойти позже, чем необходимо, что приведет к ненужной работе и недетерминированности запаздывания, что не имеет место с нетерпением [ 10 ] [ 11 ], Примерами окончательности являются исключения состояния, времени, отсутствия завершения и времени выполнения. Это императивные побочные эффекты, но даже в чистом декларативном языке (например, Haskell) в монаде императивного ввода-вывода есть состояние (примечание: не все монады являются императивными!), Подразумеваемое в распределении пространства, а время - это состояние относительно императива реальный мир. Использование lazy даже с необязательным eager приводит к утечке "лени" во внутренние сопродукты, потому что с lazy некорректность лени исходит из внешней функции (см. Пример в разделе "Не прекращение", где == - внешняя функция двоичного оператора). Это связано с тем, что копроизведения ограничены конечностью, то есть коиндуктивными типами с конечной алгеброй конечного объекта [ 11 ].

    Ленивый вызывает неопределенность в разработке и отладке функций для задержки и пространства, отладка которых, вероятно, выходит за рамки возможностей большинства программистов, из-за диссонанса между объявленной иерархией функций и порядком оценки времени выполнения. Ленивые чистые функции, оцененные с нетерпением, потенциально могут вводить ранее невидимое прекращение во время выполнения. И наоборот, активные чистые функции, оцениваемые с помощью lazy, могут потенциально вводить ранее невиданное пространство и неопределенность латентности во время выполнения.

Незавершение

Во время компиляции из-за проблемы Halting и взаимной рекурсии в полном языке Тьюринга функции не могут быть гарантированно завершены.

  • нетерпеливый

    С нетерпением, но не лень, для соединения Head "а также" Tail, если либо Head или же Tail не заканчивается, то соответственно либо List( Head(), Tail() ).tail == Tail() или же List( Head(), Tail() ).head == Head() неверно, потому что левая сторона не завершается, а правая сторона завершается.

    Тогда как с ленивыми обе стороны кончаются. Таким образом, eager слишком стремится к совместным продуктам и не завершается (включая исключения времени выполнения) в тех случаях, когда в этом нет необходимости.

  • ленивый

    С ленивым, но не страстным желанием 1 "или же" 2, если f не заканчивается, то List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail неверно, потому что левая сторона заканчивается, а правая - нет.

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

[ 10 ] Декларативные продолжения и категориальная двойственность, Филински, разделы 2.5.4. Сравнение CBV и CBN и 3.6.1 CBV и CBN в SCL.

[ 11 ] Декларативные продолжения и категориальная двойственность, Филински, разделы 2.2.1 Продукты и сопутствующие товары, 2.2.2 Терминальные и начальные объекты, 2.5.2 CBV с ленивыми продуктами и 2.5.3 CBN с готовыми побочными продуктами.

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

Обязательный: основное внимание уделяется тому, какие шаги должен предпринять компьютер, а не тому, что будет делать компьютер (напр. C, C++, Java).

Декларативный - основное внимание уделяется тому, что должен делать компьютер, а не тому, как он должен это делать (например, SQL).

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

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

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

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

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

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

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

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

Подводя итог, то:

  • императив и декларативный являются двумя противоположными стилями программирования (одни и те же имена используются для языков программирования, которые поощряют эти стили)

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

поэтому "функциональное программирование" часто называют "декларативным".

В двух словах:

Императивный язык определяет серию инструкций, которые компьютер выполняет последовательно (сделайте это, затем сделайте это).

Декларативный язык объявляет набор правил о том, какие выходные данные должны получаться из каких входных данных (например, если у вас есть A, то результатом является B). Движок применит эти правила к входным данным и выдаст выходные данные.

Функциональный язык объявляет набор математических / логических функций, которые определяют, как ввод преобразуется в вывод. например. f(y) = y * y. это тип декларативного языка.

Императив: как достичь нашей цели

   Take the next customer from a list.
   If the customer lives in Spain, show their details.
   If there are more customers in the list, go to the beginning

Декларативный: чего мы хотим достичь

   Show customer details of every customer living in Spain

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

Декларативное программирование означает любой стиль программирования, в котором ваша программа представляет собой описание проблемы или решения, но в ней явно не указано, как будет выполняться работа.

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

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

Термин перезапись (например, CASL) является еще одной формой декларативного программирования. Это включает в себя символическое преобразование алгебраических терминов. Это полностью отличается от логического программирования и функционального программирования.

императив - выражения описывают последовательность действий, которые нужно выполнить (ассоциативно)

декларативные - выражения - это объявления, которые способствуют поведению программы (ассоциативные, коммутативные, идемпотентные, монотонные)

функционал - выражения имеют значение как единственный эффект; семантика поддерживает эквациональное мышление

С тех пор как я написал свой предыдущий ответ, я сформулировал новое определение декларативного свойства, которое приводится ниже. Я также определил императивное программирование как двойственное свойство.

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

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

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

Декларативный и императивный

Декларативное свойство является странным, тупым, и его трудно уловить в технически точном определении, которое остается общим и не двусмысленным, потому что это наивное представление о том, что мы можем объявлять значение (то есть семантику) программы, не вызывая непреднамеренных побочных эффектов. Между выражением смысла и избеганием непреднамеренных эффектов существует внутреннее противоречие, которое на самом деле вытекает из теорем о неполноте программирования и нашей вселенной.

Чрезмерно упрощенно, технически неточно и часто неоднозначно определять декларативное как"что делать" и обязательно как"как делать". Неоднозначный случай - это "что" - это "как" в программе, которая выводит программу - компилятор.

Очевидно, что неограниченная рекурсия, которая делает язык Тьюринга полным, также аналогично семантике, а не только синтаксической структуре оценки (операционная семантика). Это логически пример, аналогичный теореме Гёделя: "любая полная система аксиом также несовместна ". Обдумайте противоречивую странность этой цитаты! Это также пример, который демонстрирует, как выражение семантики не имеет доказуемой границы, поэтому мы не можем доказать 2, что программа (и аналогично ее семантике) останавливается, как теорема Халтинга.

Теоремы о неполноте проистекают из фундаментальной природы нашей вселенной, которая, как указано во Втором законе термодинамики, гласит: " Энтропия(или число независимых возможностей)стремится к максимуму навсегда". Кодирование и дизайн программы никогда не заканчиваются - она ​​жива! - потому что она пытается удовлетворить потребности реального мира, а семантика реального мира всегда меняется и имеет тенденцию к увеличению возможностей. Люди никогда не перестают открывать новые вещи (в том числе ошибки в программах;-).

Чтобы точно и технически охватить это вышеупомянутое желаемое понятие в этой странной вселенной, которая не имеет границ (учтите, что "вне" нашей вселенной нет), требуется краткое, но обманчиво-непростое определение, которое будет звучать некорректно до тех пор, пока оно не будет объяснено глубоко.

Определение:


В декларативном свойстве может существовать только один возможный набор операторов, которые могут выражать каждую конкретную модульную семантику.

Императивное свойство 3 является двойственным, где семантика противоречива по составу и / или может быть выражена с помощью вариаций наборов утверждений.


Это определение декларативного является исключительно локальным в семантической области, что означает, что оно требует, чтобы модульная семантика поддерживала свое непротиворечивое значение независимо от того, где и как она была реализована и использована в глобальной области. Таким образом, каждая декларативная модульная семантика должна быть по сути ортогональной ко всем возможным другим, а не невозможным (из-за теорем о неполноте)глобальным алгоритмом или моделью для подтверждения последовательности, что также является точкой " Больше не всегда лучше" Роберта Харпера, профессора компьютерных наук в Университете Карнеги-Меллона, один из разработчиков Standard ML.

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

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

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

Язык гипертекстовой разметки, то есть HTML - язык статических веб-страниц - является примером высоко (но не совсем 3) декларативного языка, который (по крайней мере, до HTML 5) не обладал способностью выражать динамическое поведение. HTML, пожалуй, самый простой язык для изучения. Для динамического поведения императивный язык сценариев, такой как JavaScript, обычно сочетался с HTML. HTML без JavaScript соответствует декларативному определению, поскольку каждый номинальный тип (т. Е. Теги) сохраняет свое непротиворечивое значение в рамках композиции в правилах синтаксиса.

Конкурентное определение для декларативного - это коммутативные и идемпотентные свойства семантических операторов, то есть, что операторы могут быть переупорядочены и продублированы без изменения значения. Например, операторы, присваивающие значения именованным полям, могут быть переупорядочены и продублированы без изменения смысла программы, если эти имена являются модульными по отношению к какому-либо подразумеваемому порядку. Имена иногда подразумевают порядок, например, идентификаторы ячеек включают их столбец и положение строки - перемещение итога в электронной таблице меняет его значение. В противном случае эти свойства неявно требуют глобальной согласованности семантики. Как правило, невозможно разработать семантику операторов, чтобы они оставались непротиворечивыми при случайном порядке или дублировании, поскольку порядок и дублирование являются неотъемлемой частью семантики. Например, выражения "Foo существует" (или конструкция) и "Foo не существует" (и уничтожение). Если кто-то считает случайное несоответствие эндемичным для предполагаемой семантики, то он принимает это определение как достаточно общее для декларативного свойства. По сути, это определение является пустым как обобщенное определение, потому что оно пытается сделать последовательность ортогональной семантике, то есть бросить вызов факту, что универсум семантики динамически неограничен и не может быть охвачен в глобальной парадигме когерентности.

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

C, Java, C++, C#, PHP и JavaScript не являются особенно декларативными. Синтаксис Copute и синтаксис Python более декларативно связаны с намеченными результатами, то есть согласованной синтаксической семантикой, которая устраняет посторонние, так что можно легко понять код после того, как он его забыл. Copute и Haskell усиливают детерминизм операционной семантики и поощряют " не повторяться" (DRY), потому что они допускают только чисто функциональную парадигму.


2 Даже там, где мы можем доказать семантику программы, например, с помощью языка Coq, это ограничено семантикой, выраженной в типизации, и типизация никогда не сможет охватить всю семантику программы, даже для языков, которые не полный по Тьюрингу, например, с помощью HTML+CSS можно выразить несовместимые комбинации, которые, таким образом, имеют неопределенную семантику.

3 Многие объяснения неправильно утверждают, что только императивное программирование имеет синтаксически упорядоченные операторы. Я прояснил эту путаницу между императивным и функциональным программированием. Например, порядок операторов HTML не уменьшает согласованности их значения.


Изменить: я разместил следующий комментарий в блоге Роберта Харпера:

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

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

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

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

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

Как я писал в 2012 году, что похоже на аналогичное согласие с комментариями в вашем недавнем блоге, что декларативное программирование является попыткой уловить идею, что предполагаемая семантика никогда не бывает непрозрачной. Примерами непрозрачной семантики являются зависимость от порядка, зависимость от стирания семантики более высокого уровня на уровне операционной семантики (например , преобразования не являются преобразованиями, а уточненные обобщенные элементы ограничивают семантику более высокого уровня), а также зависимость от значений переменных, которые нельзя проверить (доказано). правильно) языком программирования.

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

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

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

Лези Лэмпорт написал сказку о том, как Евклид мог обойти теоремы Гёделя о неполноте, примененные к математическим доказательствам в контексте языка программирования, для сравнения между типами и логикой (соответствие Карри-Говарда и т. Д.).

Императивное программирование: рассказывать "машине", как что-то делать, и в результате произойдет то, чего вы хотите.

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

Пример императива

function makeWidget(options) {
    const element = document.createElement('div');
    element.style.backgroundColor = options.bgColor;
    element.style.width = options.width;
    element.style.height = options.height;
    element.textContent = options.txt;

    return element;
}

Пример декларативного

function makeWidget(type, txt) {
    return new Element(type, txt);
}

Note: The difference is not one of brevity or complexity or abstraction. As stated, the difference is how vs what.

Я думаю, что ваша таксономия неверна. Есть два противоположных типа императивных и декларативных. Функциональный это просто подтип декларативного. Кстати, Википедия утверждает тот же факт.

В настоящее время новый фокус: нам нужны старые классификации?

Императивные / декларативные / функциональные аспекты в прошлом были хороши для классификации универсальных языков, но в настоящее время все "большие языки" (такие как Java, Python, Javascript и т. Д.) Имеют некоторые опции (обычно фреймворки) для выражения с "другим фокусом" чем его основной (обычный императив), и выражать параллельные процессы, декларативные функции, лямбды и т. д.

Итак, хороший вариант этого вопроса: "Какой аспект хорош для классификации фреймворков сегодня?"... Важным аспектом является то, что мы можем обозначить как "стиль программирования"...

Фокус на слияние данных с алгоритмом

Хороший пример для объяснения. Как вы можете прочитать о JQuery в Википедии,

Набор основных функций jQuery - выбор элементов DOM, обход и манипулирование - с помощью его механизма выбора (...) создал новый "стиль программирования", алгоритмы слияния и структуры данных DOM

Таким образом, jQuery - лучший (популярный) пример сосредоточения внимания на "новом стиле программирования", то есть не только на объектной ориентации, это "Объединение алгоритмов и структур данных". JQuery несколько реактивен, так как электронные таблицы, но не "ориентированные на ячейки", "ориентированные на DOM-узлы"... Сравнение основных стилей в этом контексте:

  1. Без слияния: во всех "больших языках" в любом функциональном / декларативном / императивном выражении обычным является "не слияние" данных и алгоритма, за исключением некоторой объектной ориентации, то есть слияние с точки зрения строгой алгебраической структуры.

  2. Некоторое слияние: все классические стратегии слияния, в настоящее время имеют некоторую структуру, использующую его в качестве парадигмы... поток данных, программирование на основе событий (или старые специфичные для предметной области языки, такие как awk и XSLT)... Как и программирование с современными электронными таблицами, они также примеры стиля реактивного программирования.

  3. Большое слияние: это "стиль jQuery"... jQuery - это предметно-ориентированный язык, сфокусированный на "алгоритмахслияния и структурах данных DOM".
    PS: другие "языки запросов", такие как XQuery, SQL (с PL в качестве опции императивного выражения) также являются примерами объединения алгоритмов данных, но они являются островками, без объединения с другими системными модулями... Spring при использовании find()-варианты и спецификации, еще один хороший пример объединения.

Декларативное программирование - это программирование, выражающее некоторую вневременную логику между входом и выходом, например, в псевдокоде, следующий пример будет декларативным:

def factorial(n):
  if n < 2:
    return 1
  else:
    return factorial(n-1)

output = factorial(argvec[0])

Мы просто определяем здесь отношение, называемое "факториалом", и определяем отношение между выходом и входом как это отношение. Как должно быть очевидно здесь, любой структурированный язык допускает декларативное программирование в некоторой степени. Основная идея декларативного программирования - неизменяемые данные: если вы присваиваете переменную, вы делаете это только один раз, а потом никогда. Другие, более строгие определения влекут за собой отсутствие побочных эффектов, эти языки иногда называют "чисто декларативными".

Тот же результат в императивном стиле будет:

a = 1
b = argvec[0]
while(b < 2):
  a * b--

output = a

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

Говорят, что декларативные языки часто указывают "что должно быть сделано", а не "как это сделать". Я думаю, что это неправильно, декларативные программы все еще определяют, как нужно переходить от ввода к выводу, но по-другому, указанные вами отношения должны быть эффективно вычислимыми (важный термин, ищите его, если вы его не знаете). Другим подходом является недетерминированное программирование, которое на самом деле просто определяет, с какими условиями встречается результат, прежде чем ваша реализация просто исчерпает все пути проб и ошибок, пока не добьется успеха.

К чисто декларативным языкам относятся Haskell и Pure Prolog. Скользящая шкала от одного к другому будет: Pure Prolog, Haskell, OCaml, Scheme/Lisp, Python, Javascript, C--, Perl, PHP, C++, Pascall, C, Fortran, Assembly

Некоторые хорошие ответы здесь относительно отмеченных "типов".

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

  • Специфичный для домена язык или DSL- программирование: создание нового языка для решения рассматриваемой проблемы.
  • Мета-программирование: когда ваша программа пишет другие программы.
  • Эволюционное программирование: где вы создаете систему, которая постоянно совершенствуется или генерирует последовательно лучшие поколения подпрограмм.

Короче говоря, чем больше стиль программирования подчеркивает то, что (чтобы сделать), абстрагируясь от деталей того, как (чтобы сделать это), тем больше этот стиль считается декларативным. Противоположность верна для императива. Функциональное программирование связано с декларативным стилем.

Другие вопросы по тегам