Императивное и декларативное программирование
Вряд ли вы не слышали о таких понятиях, как декларативное и императивное программирование. В этой статье мы рассмотрим императивный и декларативный подход, а также основные языки программирования (programming language), которые эти подходы используют. Давайте начнем.
Если посмотреть определение в англоязычной Википедии, мы увидим приблизительно следующее:
При декларативная подходе выражается логика вычисления без отсутствия описания потока управления, тогда как в императивной парадигме программирования используются утверждения, изменяющие состояние программы.
С первого взгляда ничего не понятно, а словосочетания типа «парадигма программирования» и вовсе звучат слишком претенциозно. Такие фразы любят говорить профессоры в университетах, но это не добавляет понимания, если за ними не следуют конкретные примеры.
Давайте попробуем объяснить более простыми словами:
- Императивный подход описывает, каким образом ты что-то делаешь.
- Декларативный описывает, что именно ты делаешь.
То есть в первом случае у нас стоит вопрос «Как?», а во втором — «Что?» И все равно разница ясна лишь интуитивно, поэтому без практических примеров не обойтись. Но начать лучше стоит с метафор.
Давайте представим, что вы попросили вашего товарища нарисовать пейзаж, а как он это сделает, для вас значения не имеет — это декларативный путь, когда дается ответ на вопрос «Что именно надо сделать?»
В императивном случае ситуация следующая:
— вы попросили товарища нарисовать пейзаж;
— он попросил, к примеру, Никаса Сафронова, рассказать ему, как рисуются пейзажи;
— Никас Сафронов как мастер своего дела предоставил пошаговые инструкции — как именно нарисовать этот пейзаж.
Императивные и декларативные языки программирования
Примеры декларативных языков программирования:
Императивные языки:
Также выделяют смешанные языки:
Говоря о языках, важно понимать, что у многих декларативных языков программирования существует определенный слой императивных абстракций — не забывайте об этом.
Рассмотрим работу декларативных языков на примерах.
Декларативный язык SQL:
Нам достаточно просто взглянуть на код, чтобы понять суть происходящего. Языки программи рования в данном случае заявляют, не как что-то надо сделать, а что именно должно быть сделано, поэтому можно говорит о декларативности. То есть мы описываем желаемый результат и не углубляемся в инструкции. И для нас абсолютно не имеет значения, как будет сделана выборка пользователей из Мексики и как web-браузер распарсит article . Главное, что мы получим этих самых пользователей, а также новый header и paragraph на веб-сайте.
И снова код
Для следующего примера воспользуемся языком программирования JavaScript. Давайте представим, что мы находимся на собеседовании. Нам поставлены следующие задачи:
— написать функцию с названием double, принимающую массив чисел и возвращающую новый массив, причем каждый элемент нового массива больше исходного в 2 раза:
— написать функцию с названием add, принимающую массив и возвращающую сумму всех элементов массива:
— используя библиотеку jQuery (либо чистый язык JavaScript), выполните добавление обработчика событий click к элементу с идентификатором (id), равным btn. При нажатии выполните переключение класса highlight и замените текст на Add Highlight либо Remove Highlight с учетом того, каково текущее состояние элемента.
Задания, есть, давайте попробуем их решить. Начнем с императивной парадигмы и воспользуемся наиболее распространенными подходами.
Теперь поговорим, что общего у этих примеров, и почему они являются именно императивными, а не декларативными:
- Во всех случаях описывается, как именно решить проблему, то есть явно указываются все шаги по решению поставленной задачи.
- Следующий момент не так очевиден для тех, кто имеет привычку думать декларативно либо даже функционально. В каждом из вышеописанных примеров осуществляется изменение какого-нибудь состояния. И если в первых 2-х случаях происходит изменение переменной results, то в 3-м случае состояние находится в самой DOM, причем его мы тоже меняем.
- Если говорить субъективно, то код можно считать, по сути, нечитаемым. А все потому, что с первого раза вы вряд ли поймете, что конкретно происходит, — вместо этого вам придется внимательно читать программный код построчно.
Переходим к декларативному подходу. Наша цель, как и прежде, заключается в том, чтобы решить все вышеописанные задачи. Также каждое решение должно описывать, что конкретно происходит, а также быть читаемым и неизменяемым.
Теперь стало гораздо лучше, не находите?
Обратите внимание, что в первых 2-х примерах применяются встроенные в язык JavaScript методы: map и reduce. То есть в нашем случае декларати вное программирование — это абстракции над императивными реализациями, но на деле нас мало волнует, как эти методы реализованы. Мы тоже не меняем состояния, да и сам программный код стал более читаемым.
В 3-м примере при написании кода мы пошли на хитрость, так как задействовали библиотеку языка JavaScript под названием React. Но важно не это, а то, что все 3 императивные ошибки нами исправлены. Да и само программи рование на React хорошо еще и тем, что предоставляет возможность создавать декларативные пользовательские интерфейсы. Если посмотреть на тот же компонент Btn, сразу становится понятно, как конкретно станет выглядеть интерфейс. А еще состояния «живут» не в DOM, а непосредственно в React-компоненте.
Нельзя не вспомнить и еще одно важное преимущество декларативного кода: он является контекстно-независимым. Все это означает, что данный код вы сможете использовать практически в любой программе без каких-либо изменений.
- «Imperative vs Declarative Programming» — https://ui.dev/imperative-vs-declarative-programming/;
- «Declarative vs Imperative Programming» — https://codeburst.io/declarative-vs-imperative-programming-a8a7c93d9ad2.
Императивное и декларативное программирование простым языком — объясняют эксперты
Начинающему программисту несложно запутаться в различных терминах — взять только объектно-ориентированное, динамическое, императивное и декларативное программирование. Спросили у экспертов, что из себя представляют два последних подхода.
Императивные языки, такие, как Java, Python, JavaScript, C, C++ занимают доминирующее положение в индустрии ПО, соответственно императивное программирование — самое распространённое. Смысл его в том, что императивная программа содержит прямые указания, что должен сделать компьютер и в каком порядке должны выполняться инструкции. Этот подход легко понять программисту, а компилятору — легко породить достаточно эффективный код.
Декларативное программирование распространено не так обширно, как императивное, хотя оказывает большое влияние на мейнстрим. Смысл декларативного программирования в том, что программы пишут в виде некоторых ограничений и правил. Логические языки, такие, как Пролог, предлагают описывать ограничения в виде фактов и правил.
В функциональных языках (другой вариант декларативного программирование) описывают программу в виде функций. Отличие от функций в императивном программировании заключается в том, что функции в функциональном языке являются математическими в том смысле, что они устанавливают отношение между аргументом и результатом, и не могут изменять никаких переменных во время вычислений.
Вообще в декларативных языках обычно отсутствует изменение переменных или обычно спрятано за каким-либо специальным механизмом.
Самый популярный язык РСУБД SQL так же является декларативным. На нём описывается конечный результат, а способ его получения генерируется сервером СУБД исходя из множества факторов.
Декларативное программирование может являться более сложным в понимании, но позволяет писать более безопасный и поддерживаемый код, который легко параллелится. А компиляторы декларативных языков имеют больше возможностей при оптимизации программ.
Конечно, практически все основные языки сочетают в себе элементы и декларативного и императивного программирования. Огромное влияние оказывает функциональное программирование на JavaScript, Java, C++, C# и т.д.
Декларативное программирование — это парадигма программирования, в которой задаётся спецификация решения задачи: описывается, что представляет собой проблема и ожидаемый результат, но без описания способа достижения этого результата. Зачастую декларативные программы не используют понятия состояния и, в частности, не содержат переменных и операторов присваивания, обеспечивая ссылочную прозрачность. К подвидам декларативного программирования часто относят и функциональное программирование. Декларативные компьютерные языки часто не полны по Тьюрингу, так как теоретически не всегда возможно порождение исполняемого кода по декларативному описанию.
Императивное программирование — это парадигма программирования, в которой задаётся последовательность действий, необходимых для получения результата. В нём используются переменные, операторы присваивания и составные выражения.
Несмотря на то, что исторически первым был применен декларативный подход в программировании, первые языки программирования компьютеров (машинный, ассемблер, фортран, алгол, кобол) были императивными в силу простоты подхода.
Сейчас широко распространены как узкоспециализированные декларативные языки программирования (HTML + CSS, SVG, VRML, SQL, lex/VACC), в том числе функциональные (Haskell, Erlang, Scala), так и императивные языки (C/C++/C#, Java, Go, Rust, Python). Однако практически все современные языки программирования общего назначения высокого уровня, за исключением некоторых функциональных, относятся к императивным языкам.
Выбор той или иной парадигмы программирования — императивной или функциональной — определяется, главным образом, требованиями к программе и набором достоинств и недостатков каждой из парадигм. Так, например, независимость функций по данным в функциональных языках и отсутствие побочных эффектов чрезвычайно сокращает количество ошибок и позволяет эффективно распараллеливать код. Поэтому для создания высоконагруженных систем с высоким уровнем параллельных вычислений более оправданно выбирать один из функциональных языков.
С другой стороны, неизменность входных данных в функциональных языках программирования затрудняет создание систем, активно выполняющих ввод-вывод и модификацию уже имеющихся данных. Для реализации таких систем предпочтительнее выбирать императивные языки.
Императивное программирование — это парадигма, основанная на составлении алгоритма действий (инструкций/команд), которые изменяют состояние (информацию/данные/память) программы. Первыми языками программирования, основанными на таком подходе, были машинные коды и ассемблеры. Фактически, программа на этих языках — это код, который выполняется компьютером сразу, без предварительной компиляции. Из языков высокого уровня, требующих компиляции исходного кода программы в машинный код (или интерпретации), к императивным можно отнести C, C++, Java.
Декларативное программирование — это парадигма, при которой описывается желаемый результат, без составления детального алгоритма его получения. В пример можно привести HTML и SQL. При создании HTML мы с помощью тегов описываем, какую хотим получить страничку в браузере, а не то, как нарисовать на экране заголовок статьи, оглавление и текст. В SQL, если нам нужно посчитать количество сотрудников с фамилией «Сидоров», мы напишем SELECT count(*) FROM employee WHERE last_name = ‘Сидоров’; . Тут ничего не сказано про то, в каком файле или области памяти находятся данные по сотрудникам, как именно выбрать из них всех Сидоровых и нужно ли вообще это делать для подсчёта их количества.
Рассмотрим ещё один пример. Допустим, мы хотим приготовить обед.
В императивной парадигме это выглядит как-то так:
- купить мясо, огурцы, помидоры, соль;
- порезать мясо, посолить;
- поставить сковородку на плиту;
- …
В декларативной: хочу на обед жареное мясо с овощами (неплохо звучит, правда? :)).
Вроде бы различия очевидны. Однако, императивный язык не мешает обобщить и автоматизировать отдельные задачи. Можно реализовать некий «слой» кода, библиотеки, которые будут «уметь» выполнять отдельные этапы алгоритма: определять по рецепту, есть ли в наличии необходимые продукты, заказывать их доставку, пользоваться плитой и т.д. Получится, что программный код императивного языка программирования, использующий такие библиотеки, уже не будет по своей структуре так уж сильно отличаться от декларативного.
На практике, при написании кода и выбора подхода, разработчик отталкивается не только от возможностей и ограничений языка программирования, но и удобства использования той или иной парадигмы в данном конкретном случае.