Чем отличается императивное программирование от декларативного
Предыстория
Не так давно я была в активном поиске работы и просматривала кучу сайтов с вакансиями/проектами. На некоторых из них «проходные» вопросы можно посмотреть еще до подачи отклика.
Большинство было вполне обычными, типа «сколько лет вы пользуетесь фреймворком Х?», но один мне показался интересным (я даже туда откликнулась, но #меняневзяли).
Это собственно и был вопрос из заголовка. И я решила разобраться. А лучший способ понять — это, как известно, объяснить. Так что добро пожаловать под кат.
В чем же отличия?
Для того, чтобы описать отличия, нужно сначала понять, что это в принципе такое. В вольном пересказе.
Императивный стиль
Это такой стиль программирования, при котором вы описываете, как добиться желаемого результата. Например я пишу:
Это что ни на есть декларативный стиль, но при этом с примесью императивного.
Декларативный стиль
Такой стиль, в котором вы описываете, какой именно результат вам нужен.
И получатель такого сообщения уже сам разбирается, какие шаги для этого надо предпринять.
Почему пример из части про императивность на самом деле с примесью декларативности? Ну хотя бы потому что получатель должен знать, что такое сковорода и яйца.
Пример на JS
Все языки программирования высокого уровня позволяют писать в смешанном стиле и, на самом деле, четкого разделения даже эти два примера не показывают.
Наверное, правильно называть один стиль «более декларативным», а другой — «более императивным».
Императивное программирование
В этой статье вы прочитаете что такое «Императивное программирование», зачем нужно, думаю будет очень интересно.
Также посмотрите статью «Что такое метапрограммирование», тоже очень интересно.
Императивное программирование:
Императивное программирование (также процедурное программирование ) — одна из парадигм программирования или способов, с помощью которых решение проблем формулируется на языке программирования.
Императивное программирование описывает расчет с помощью последовательности команд и определяет точную процедуру ( алгоритм ) решения задачи.
Программа представляет собой набор переменных, которые в зависимости от оценки условий изменяют свое состояние с помощью команд.
Основным методом императивного программирования является процедурное программирование, поэтому эти термины часто путают.
Императивный подход тоже близок обычному человеку. Например, рецепты приготовления или инструкции по сборке купленных продуктов также являются пошаговыми командами, и каждый заказ привязан к определенному состоянию еды или комплектности продукта в зависимости от условий.
Этот естественный образ жизни людей в точности соответствует парадигме императивного программирования, и поэтому неудивительно, что большинство языков программирования и аппаратных реализаций большинства компьютеров являются императивными.
Аппаратное обеспечение, такое как повар / ассемблер, затем выполняет команду за командой в точности, как это соответствует императивному подходу.
Только отдельные команды являются инструкциями машинного кода, которые выполняет оборудование, а текущее состояние выражает содержимое памяти.
В более высоких императивных языках затем используются переменные и более сложные команды (т.е. выражения и функции ), но они по-прежнему исповедуют ту же императивную парадигму.
Первыми императивными языками программирования были машинные языки отдельных компьютеров.
На этих языках были только очень простые инструкции, которые позволяли очень легко реализовать аппаратное обеспечение, но затрудняли создание сложных программ.
Первым языком, устраняющим барьеры машинного кода для создания сложных программ, был FORTRAN, созданный Джоном Бэкусом в IBM в 1954 году.
FORTRAN принес возможности (такие как именование переменных, составные выражения, подпрограммы и многое другое), которые являются частью императивных языков и по сей день.
Тогда языки COBOL (1960) и BASIC (1964) были попыткой адаптировать синтаксис программирования к синтаксису естественного языка — английского.
Для нужд Министерства обороны США в 1978 году, после четырех лет обобщения требований, Джин Ичбиа и команда Honeywell приступили к разработке языка Ada. Спецификация была впервые опубликована в 1983 году и пересматривалась в 1995 и 2005/6 гг.
В 1980-х годах интерес к объектно-ориентированному программированию возрос. Новые языки тогда исповедовали императивный стиль, но добавили поддержку объектов.
Деление:
В конечном итоге императивное программирование можно разделить на три группы.
Наивная парадигма иногда понимается как отдельная парадигма, а еще чаще она даже не упоминается среди парадигм программирования.
Наивные языки характеризуются своего рода вездесущим хаотизмом, обычно имеют бессистемный синтаксис и семантику.
Неструктурированная парадигма очень близка ассемблерам. Программы представляют собой линейные последовательности команд, и переходы в них выполняются командой типа «перейти к» — т.е. «перейти к (строке)».
Кроме того, в ранних языках этого стиля все строки программы были пронумерованы, и переходы можно было делать только путем указания определенного номера строки, что было очень непрактично.
Структурированная парадигма. Из-за непрактичности команды перехода «перейти к» (главным образом в том, что структура программы практически не дает информации о ее выполнении, что значительно усложняет ее отладку), была создана структурированная парадигма.
Основные типы команд:
В императивном программировании используются три основные группы команд.
Назначение обычно выполняет операцию с информацией, хранящейся в памяти, и сохраняет результат в памяти для дальнейшего использования.
Циклы позволяют повторять последовательность команд несколько раз подряд. Затем можно точно определить количество повторений или последовательность может повторяться до тех пор, пока указанное условие не изменится.
Команды ветвления позволяют выполнять определенную часть команд только при соблюдении соответствующего условия.
В противном случае этот раздел пропускается, и выполнение следующих сразу же команд продолжается.
Отличия в противоположном подходе:
Благодаря этому можно сохранить множество ошибок, возникающих, в основном, путем записи множества функций и методов одновременно в одну глобальную переменную.
С другой стороны, при императивном подходе программист может оптимизировать программу так, как ему нужно.
При декларативном подходе они должны полагаться на компилятор, который может не выбирать алгоритм, который был бы более выгодным в данный момент.
Вывод:
В этой статье вы прочитали что такое «Императивное программирование», зачем нужно и какие языки программирования работают на этом принципе.
Основные принципы программирования: императивное и декларативное программирование
Авторизуйтесь
Основные принципы программирования: императивное и декларативное программирование
Рассказывает Тайлер МакГиннис, Google Developer Expert
Вы наверняка слышали о таких понятиях, как императивное и декларативное программирование, и скорее всего гуглили определения. И поэтому вы наверняка видели что-то подобное: «Императивное программирование — это описание того, как ты делаешь что-то, а декларативное — того, что ты делаешь. Это объяснение отлично подходит тем, кто уже разобрался в этом вопросе — но не новичкам.
Самым сложным является тот факт, что разница между декларативным и императивным подходами часто понятна интуитивно, но её сложно задать определением. Я общался со многими программистами и пришёл к заключению, что лучшее объяснение — это сочетание метафор и примеров кода. Итак, начнём.
Допустим, вы поняли, что слишком много времени уделяли работе, и решили пригласить свою половинку на свидание. Вы пришли в ресторан, подошли к администратору и сказали…
Императивный подход (как): Я вижу, что тот угловой столик свободен. Мы пойдём туда и сядем там.
Декларативный подход (что): Столик для двоих, пожалуйста.
Императивный подход означает то, как вы займёте место. Вы должны перечислить все шаги этого процесса. Декларативный же подход заявляет, что вам нужен столик на двоих.
Я задам вам вопрос и хочу, чтобы вы придумали и императивный, и декларативный подход.
«Я у Ашана. Как мне пройти до твоего дома?»
«Пройди через северный выход парковки и поверни налево. Сядь на автобус 678 и выйди на остановке «Улица Победы». Поверни направо, как если бы ты шёл в Икею. Иди прямо и поверни направо у первого светофора. На следующем светофоре поверни налево. Номер моего дома — 134.»
Мой адрес: Энск, улица Победы, дом 134.
Неважно, как я попаду к твоему дому, важно, на какой машине я приеду. У неё будет или императивная механическая КПП, или декларативная автоматическая КПП. Достаточно метафор?
Прежде чем мы обратимся к коду, важно понять, что многие декларативные подходы имеют определённый слой императивных абстракций. Вернёмся к нашим примерам. Декларативное обращение к сотруднику ресторана подразумевает, что он знает все императивные шаги по предоставлению вам столика. Знание адреса подразумевает, что у вас есть GPS-навигатор, который знает императивные шаги по составлению маршрута. У автомобиля с автоматической КПП есть определённый слой абстракций над передключением передач.
Итак, я повторюсь: многие (если не все) декларативные подходы имеют слой неких императивных абстракций.
Теперь мы перейдём от приятных метафор к реальному коду. Сперва посмотрим, какие языки являются декларативными, а какие — императивными:
Вот типичные примеры на SQL и HTML:
Пока неплохо. Давайте рассмотрим примеры на JavaScript.
Представьте, что вы на собеседовании. Откройте консоль и ответьте на следующие вопросы.
Давайте взглянем на самые распространённые подходы к решению этих задач, которые являются императивными.
Разобравшись, что общего у этих императивных примеров, мы поймём, что именно делает их императивными.
А теперь взглянем на декларативные примеры. Их цель — решить все проблемы, описанные выше. Поэтому каждый пример должен описывать, что происходит, быть неизменяемым и читаемым.
Ну а третий? В нём я немного схитрил, использовав React — но обратите внимание, что все три императивные ошибки исправлены. React замечателен тем, что в нём вы можете создавать декларативные пользовательские интерфейсы. Смотря на компонент Btn, сразу понятно, как будет выглядеть интерфейс. Кроме того, состояния «живут» не в DOM, а в самом React-компоненте.
Ещё одним преимуществом является то, что декларативный код является контекстно-независимым. Это значит, что его можно использовать в любой программе без изменений.
«Забытые» парадигмы программирования
Получилось так, что те парадигмы, которые раньше потом и кровью пробивались в свет через орды приверженцев традиционных методов постепенно забываются. Эти парадигмы возникли на заре программирования и то, почему они возникали, какие преимущества они давали и почему используются до сих пор полезно знать любому разработчику.
Ладно. Введение это очень весело, но вы его все равно не читаете, так что кому интересно — добро пожаловать под кат!
Императивное программирование
Исторически сложилось так, что подавляющее большинство вычислительной техники, которую мы программируем имеет состояние и программируется инструкциями, поэтому первые языки программирования в основном были чисто императивными, т.е. не поддерживали никаких парадигм кроме императивной.
Это были машинные коды, языки ассемблера и ранние высокоуровневые языки, вроде Fortran.
Ключевые моменты:
В этой парадигме вычисления описываются в виде инструкций, шаг за шагом изменяющих состояние программы.
В низкоуровневых языках (таких как язык ассемблера) состоянием могут быть память, регистры и флаги, а инструкциями — те команды, что поддерживает целевой процессор.
В более высокоуровневых (таких как Си) состояние — это только память, инструкции могут быть сложнее и вызывать выделение и освобождение памяти в процессе своей работы.
В совсем высокоуровневых (таких как Python, если на нем программировать императивно) состояние ограничивается лишь переменными, а команды могут представлять собой комплексные операции, которые на ассемблере занимали бы сотни строк.
Основные понятия:
Порожденные понятия:
— Присваивание
— Переход
— Память
— Указатель
Языки поддерживающие данную парадигму:
Как основную:
— Языки ассемблера
— Fortran
— Algol
— Cobol
— Pascal
— C
— C++
— Ada
Как вспомогательную:
— Python
— Ruby
— Java
— C#
— PHP
— Haskell (через монады)
Стоит заметить, что большая часть современных языков в той или иной степени поддерживает императивное программирование. Даже на чистом функциональном языке Haskell можно писать императивно.
Структурное программирование
Структурное программирование — парадигма программирования (также часто встречающееся определение — методология разработки), которая была первым большим шагом в развитии программирования.
Основоположниками структурного программирования были такие знаменитые люди как Э. Дейкстра и Н. Вирт.
Языками-первопроходцами в этой парадигме были Fortran, Algol и B, позже их приемниками стали Pascal и C.
Ключевые моменты:
Эта парадигма вводит новые понятия, объединяющие часто используемые шаблоны написания императивного кода.
В структурном программировании мы по прежнему оперируем состоянием и инструкциями, однако вводится понятие составной инструкции (блока), инструкций ветвления и цикла.
Благодаря этим простым изменениям возможно отказаться от оператора goto в большинстве случаев, что упрощает код.
Иногда goto все-же делает код читабельнее, благодаря чему он до сих пор широко используется, несмотря на все заявления его противников.
Основные понятия:
— Блок
— Цикл
— Ветвление
Языки поддерживающие данную парадигму:
Как основную:
Как вспомогательную:
— C#
— Java
— Python
— Ruby
— JavaScript
Поддерживают частично:
— Некоторые макроассемблеры (через макросы)
Опять-же большая часть современных языков поддерживают структурную парадигму.
Процедурное программирование
Опять-же возрастающая сложность программного обеспечения заставила программистов искать другие способы описывать вычисления.
Собственно еще раз были введены дополнительные понятия, которые позволили по-новому взглянуть на программирование.
Этим понятием на этот раз была процедура.
В результате возникла новая методология написания программ, которая приветствуется и по сей день — исходная задача разбивается на меньшие (с помощью процедур) и это происходит до тех пор, пока решение всех конкретных процедур не окажется тривиальным.
Ключевые моменты:
Процедура — самостоятельный участок кода, который можно выполнить как одну инструкцию.
В современном программировании процедура может иметь несколько точек выхода (return в C-подобных языках), несколько точек входа (с помощью yield в Python или статических локальных переменных в C++), иметь аргументы, возвращать значение как результат своего выполнения, быть перегруженной по количеству или типу параметров и много чего еще.
Основные понятия:
Порожденные понятия:
— Вызов
— Аргументы
— Возврат
— Рекурсия
— Перегрузка
Языки поддерживающие данную парадигму:
Как основную:
— C
— C++
— Pascal
— Object Pascal
Как вспомогательную:
— C#
— Java
— Ruby
— Python
— JavaScript
Поддерживают частично:
— Ранний Basic
Стоит отметить, что несколько точек входа из всех этих языков поддерживаются только в Python.
Модульное программирование
Который раз увеличивающаяся сложность программ заставила разработчиков разделять свой код. На этот раз процедур было недостаточно и в этот раз было введено новое понятие — модуль.
Забегая вперед скажу, что модули тоже оказались неспособны сдержать с невероятной скоростью растущую сложность ПО и в последствии появились пакеты (это тоже модульное программирование), классы (это уже ООП), шаблоны (обобщенное программирование).
Программа описанная в стиле модульного программирования — это набор модулей. Что внутри, классы, императивный код или чистые функции — не важно.
Благодаря модулям впервые в программировании появилась серьезная инкапсуляция — возможно использовать какие-либо сущности внутри модуля, но не показывать их внешнему миру.
Ключевые моменты:
Модуль — это отдельная именованная сущность программы, которая объединяет в себе другие программные единицы, близкие по функциональности.
Например файл List.mod включающий в себя класс List
и функции для работы с ним — модуль.
Папка Geometry, содержащая модули Shape, Rectangle и Triangle — тоже модуль, хоть и некоторые языки разделяют понятие модуля и пакета (в таких языках пакет — набор модулей и/или набор других пакетов).
Модули можно импортировать (подключать), для того, чтобы использовать объявленные в них сущности.
Основные понятия:
Порожденные понятия:
Языки поддерживающие данную парадигму:
Как основную:
— Haskell
— Pascal
— Python
Как вспомогательную:
— Java
— C#
— ActionScript 3
Поддерживают частично:
— C/C++
В некоторых языках для модулей введены отдельные абстракции, в других же для реализации модулей можно использовать заголовочные файлы (в C/C++), пространства имен, статические классы и/или динамически подключаемые библиотеки.
Вместо заключения
В данной статье я не описал популярные сейчас объектно-ориентированное, обобщенное и функциональное программирование. Просто потому, что у меня есть свое, довольно радикальное мнение на этот счет и я не хотел разводить холивар. По крайней мере сейчас. Если тема окажется полезной для сообщества я планирую написать несколько статей, изложив основы каждой из этих парадигм подробно.
Также я ничего не написал про экзотические парадигмы, вроде автоматного, аппликативного, аспект/агент/компонент-ориентированного программирования. Я не хотел делать статью сильно большой и опять-же если тема будет востребована, я напишу и об этих парадигмах, возможно более подробно и с примерами кода.
Императивное и декларативное программирование
Вряд ли вы не слышали о таких понятиях, как декларативное и императивное программирование. В этой статье мы рассмотрим императивный и декларативный подход, а также основные языки программирования (programming language), которые эти подходы используют. Давайте начнем.
Если посмотреть определение в англоязычной Википедии, мы увидим приблизительно следующее:
При декларативная подходе выражается логика вычисления без отсутствия описания потока управления, тогда как в императивной парадигме программирования используются утверждения, изменяющие состояние программы.
С первого взгляда ничего не понятно, а словосочетания типа «парадигма программирования» и вовсе звучат слишком претенциозно. Такие фразы любят говорить профессоры в университетах, но это не добавляет понимания, если за ними не следуют конкретные примеры.
Давайте попробуем объяснить более простыми словами:
То есть в первом случае у нас стоит вопрос «Как?», а во втором — «Что?» И все равно разница ясна лишь интуитивно, поэтому без практических примеров не обойтись. Но начать лучше стоит с метафор.
Давайте представим, что вы попросили вашего товарища нарисовать пейзаж, а как он это сделает, для вас значения не имеет — это декларативный путь, когда дается ответ на вопрос «Что именно надо сделать?»
В императивном случае ситуация следующая:
— вы попросили товарища нарисовать пейзаж;
— он попросил, к примеру, Никаса Сафронова, рассказать ему, как рисуются пейзажи;
— Никас Сафронов как мастер своего дела предоставил пошаговые инструкции — как именно нарисовать этот пейзаж.
Императивные и декларативные языки программирования
Примеры декларативных языков программирования:
Императивные языки:
Также выделяют смешанные языки:
Говоря о языках, важно понимать, что у многих декларативных языков программирования существует определенный слой императивных абстракций — не забывайте об этом.
Рассмотрим работу декларативных языков на примерах.
Декларативный язык SQL:
Язык HTML:
И снова код
Для следующего примера воспользуемся языком программирования JavaScript. Давайте представим, что мы находимся на собеседовании. Нам поставлены следующие задачи:
— написать функцию с названием double, принимающую массив чисел и возвращающую новый массив, причем каждый элемент нового массива больше исходного в 2 раза:
— написать функцию с названием add, принимающую массив и возвращающую сумму всех элементов массива:
— используя библиотеку jQuery (либо чистый язык JavaScript), выполните добавление обработчика событий click к элементу с идентификатором (id), равным btn. При нажатии выполните переключение класса highlight и замените текст на Add Highlight либо Remove Highlight с учетом того, каково текущее состояние элемента.
Задания, есть, давайте попробуем их решить. Начнем с императивной парадигмы и воспользуемся наиболее распространенными подходами.
Теперь поговорим, что общего у этих примеров, и почему они являются именно императивными, а не декларативными:
Переходим к декларативному подходу. Наша цель, как и прежде, заключается в том, чтобы решить все вышеописанные задачи. Также каждое решение должно описывать, что конкретно происходит, а также быть читаемым и неизменяемым.
Теперь стало гораздо лучше, не находите?
Обратите внимание, что в первых 2-х примерах применяются встроенные в язык JavaScript методы: map и reduce. То есть в нашем случае декларати вное программирование — это абстракции над императивными реализациями, но на деле нас мало волнует, как эти методы реализованы. Мы тоже не меняем состояния, да и сам программный код стал более читаемым.
В 3-м примере при написании кода мы пошли на хитрость, так как задействовали библиотеку языка JavaScript под названием React. Но важно не это, а то, что все 3 императивные ошибки нами исправлены. Да и само программи рование на React хорошо еще и тем, что предоставляет возможность создавать декларативные пользовательские интерфейсы. Если посмотреть на тот же компонент Btn, сразу становится понятно, как конкретно станет выглядеть интерфейс. А еще состояния «живут» не в DOM, а непосредственно в React-компоненте.
Нельзя не вспомнить и еще одно важное преимущество декларативного кода: он является контекстно-независимым. Все это означает, что данный код вы сможете использовать практически в любой программе без каких-либо изменений.










