Основные принципы программирования: императивное и декларативное программирование
Авторизуйтесь
Основные принципы программирования: императивное и декларативное программирование
Рассказывает Тайлер МакГиннис, Google Developer Expert
Вы наверняка слышали о таких понятиях, как императивное и декларативное программирование, и скорее всего гуглили определения. И поэтому вы наверняка видели что-то подобное: «Императивное программирование — это описание того, как ты делаешь что-то, а декларативное — того, что ты делаешь. Это объяснение отлично подходит тем, кто уже разобрался в этом вопросе — но не новичкам.
Самым сложным является тот факт, что разница между декларативным и императивным подходами часто понятна интуитивно, но её сложно задать определением. Я общался со многими программистами и пришёл к заключению, что лучшее объяснение — это сочетание метафор и примеров кода. Итак, начнём.
Допустим, вы поняли, что слишком много времени уделяли работе, и решили пригласить свою половинку на свидание. Вы пришли в ресторан, подошли к администратору и сказали…
Императивный подход (как): Я вижу, что тот угловой столик свободен. Мы пойдём туда и сядем там.
Декларативный подход (что): Столик для двоих, пожалуйста.
Императивный подход означает то, как вы займёте место. Вы должны перечислить все шаги этого процесса. Декларативный же подход заявляет, что вам нужен столик на двоих.
Я задам вам вопрос и хочу, чтобы вы придумали и императивный, и декларативный подход.
«Я у Ашана. Как мне пройти до твоего дома?»
«Пройди через северный выход парковки и поверни налево. Сядь на автобус 678 и выйди на остановке «Улица Победы». Поверни направо, как если бы ты шёл в Икею. Иди прямо и поверни направо у первого светофора. На следующем светофоре поверни налево. Номер моего дома — 134.»
Мой адрес: Энск, улица Победы, дом 134.
Неважно, как я попаду к твоему дому, важно, на какой машине я приеду. У неё будет или императивная механическая КПП, или декларативная автоматическая КПП. Достаточно метафор?
Прежде чем мы обратимся к коду, важно понять, что многие декларативные подходы имеют определённый слой императивных абстракций. Вернёмся к нашим примерам. Декларативное обращение к сотруднику ресторана подразумевает, что он знает все императивные шаги по предоставлению вам столика. Знание адреса подразумевает, что у вас есть GPS-навигатор, который знает императивные шаги по составлению маршрута. У автомобиля с автоматической КПП есть определённый слой абстракций над передключением передач.
Итак, я повторюсь: многие (если не все) декларативные подходы имеют слой неких императивных абстракций.
Теперь мы перейдём от приятных метафор к реальному коду. Сперва посмотрим, какие языки являются декларативными, а какие — императивными:
Вот типичные примеры на SQL и HTML:
Пока неплохо. Давайте рассмотрим примеры на JavaScript.
Представьте, что вы на собеседовании. Откройте консоль и ответьте на следующие вопросы.
Давайте взглянем на самые распространённые подходы к решению этих задач, которые являются императивными.
Разобравшись, что общего у этих императивных примеров, мы поймём, что именно делает их императивными.
А теперь взглянем на декларативные примеры. Их цель — решить все проблемы, описанные выше. Поэтому каждый пример должен описывать, что происходит, быть неизменяемым и читаемым.
Ну а третий? В нём я немного схитрил, использовав React — но обратите внимание, что все три императивные ошибки исправлены. React замечателен тем, что в нём вы можете создавать декларативные пользовательские интерфейсы. Смотря на компонент Btn, сразу понятно, как будет выглядеть интерфейс. Кроме того, состояния «живут» не в DOM, а в самом React-компоненте.
Ещё одним преимуществом является то, что декларативный код является контекстно-независимым. Это значит, что его можно использовать в любой программе без изменений.
Можно ли использовать декларативный и императивный стили написания программ одновременно?
При разработке своего собственного языка программирования автор должен определиться, какими свойствами его язык должен обладать, ведь в конечном итоге именно это и будет определять назначение языка и особенности его использования. Причем некоторые свойства настолько сильно влияют на итоговую концепцию языка и его синтаксис, что впоследствии их изменение невозможно в принципе, либо новый синтаксис получается очень запутанным и непонятным. Наверно из-за этого многие свойства языков программирования рассматриваются и изучаются как взаимоисключающие.
До недавнего времени я считал, что императивная и декларативная парадигмы написания программ являются антагонистами и взаимоисключающими понятиями. Ведь выбор стиля написания кода, изначально определяется автором языка еще на стадии его проектирования и оказывает влияние на все последующие аспекты.
Но сейчас я думаю, что это не верно. Императивный и декларативный стили программирования не являются взаимоисключающими, а писать программы только в одной из указанных парадигм подталкивают правила синтаксиса, реализованные в языке!
Другими словами, использовать только императивную или только декларативную парадигмы разработчик вынужден не потому, что он не умеет или не хочет писать код по другому, а из-за того, что все более-менее мейнстримные языки программирования ориентированы на использование только одной парадигмы. И выбор только одной парадигмы написания кода, это ограничение, которое навязано программистам создателями языка. Ведь если они изначально ориентировались только на одну концепцию разработки кода, то и разрабатывали синтаксис языка в соответствии с этой парадигмой.
Данная статья — размышление о совместимости декларативной и императивной парадигм программирования и возможности их одновременного использования в рамках одного языка программирования одновременно.
Что такое «Декларативное программирование»?
Вначале мне захотелось разобраться, а существуют ли чисто декларативные языки программирования? Такие, которые могут полностью обходятся без императивных конструкций?
Но чуть не споткнулся уже в самом начале, т.к. даже само определение «декларативное программировании», в вики описывается следующим образом:
Декларати́вное программи́рование — парадигма программирования, в которой задаётся спецификация решения задачи, то есть описывается, что представляет собой проблема и ожидаемый результат. Противоположностью декларативного является императивное программирование, описывающее на том или ином уровне детализации, как решить задачу и представить результат.
Уже в изначальном определении закладывается противопоставление парадигм, а дальше идут рассуждения о «направлении» программирования. И хоть вики сама и не является авторитетным источником информации, но даже такая заготовка статьи смогла показать направление поиска ответов на первый поставленный вопрос.
И заставил задуматься один абзац в конце статьи следующего содержания:
«Чисто декларативные» компьютерные языки зачастую неполны по Тьюрингу — так как теоретически не всегда возможно порождение исполняемого кода по декларативному описанию. Это иногда приводит к спорам о корректности термина «декларативное программирование» (менее спорным является «декларативное описание решения» или, что то же самое, «декларативное описание задачи»).
Судя по наличию такой оговорки, ставится под сомнение наличие «чисто декларативных» языков. И как только пришлось задуматься над это мыслью, то сразу стало очевидным, что какой бы декларативный язык программирования не существовал, то в нем должна быть как минимум одна императивная конструкция, которая будет запускать поиск решения! Ведь любой синоним слова «выполнить» или «запустить» по определению будет императивом!
Например в языке Prolog, который обычно и приводят в качестве примера декларативного языка программирования, «замаскированным» императивным оператором является знак вопроса. Он кажется естественным и логичным, как же без него? Но по своей сути, это императивная конструкция!
Тоже самое касается и языка SQL. Он тоже как бы декларативный, но неожиданно все его команды являются императивными по сути. SELECT, CREATE, ALTER, DROP, INSERT, UPDATE, DELETE и т. д., а декларативными являются только описания условий их выполнения!
В результате, у меня так и не получилось найти чисто-декларативный язык программирования без императивных операторов (может быть, Вики права и такого языка программирования и вовсе не существует?).
А что отличает декларативные языки программирования от императивных?
Согласно определению, декларативными языками программирования их называют из-за возможности записать некие условия в декларативном стиле. Но кроме этого, в декларативных языках как правило присутствует и некий внутренний механизм поиска решений. Подобный «вычислитель» есть и в SQL и Prolog и во многих других декларативных языках.
Интересно, а является ли внутренняя система поиска решений обязательным признаком декларативного стиля программирования или это только особенность конкретного языка программирования, которая не зависит от декларативного стиля написания кода?
Чтобы проверить данные предположения, можно было попробовать написать классическую декларативную программу на императивном языке, у которого отсутствует внутренний механизм поиска решений.
А заодно, таким образом можно будет и изучить вопрос, получится ли написать программу в декларативном стиле используя обычный императивный язык программирования?
Для примера, решил попробовать повторить уже классическую программу в декларативном стиле на Прологе.
Получилась вот такая функционально — эквивалентная программа на С++, которая максимально приближена по стилю к декларативному прототипу:
Конечно, текст на С++ получается значительно многословней, чем вариант на Прологе, но по сути, это почти дословное повторение декларативного стиля написания кода. Тем более, не стоит забывать про рассуждения в начале статьи об изначальном выборе концепции при создании языка и «принуждении» программистов пользоваться только одной, изначально выбранной парадигмой разработки.
Таким образом, можно с высокой долей уверенности утверждать, что теоретически можно писать код одновременно в разных стилях.
Но что тогда мешает в рамках одного языка программирования писать с использованием императивного и декларативного стиля? Неужели только убежденность создателей языков в том, что императивный и декларативный стили программирования являются взаимоисключающими? И только из-за этого получаются языки программирования, синтаксис которых подходит для использования одной единственной парадигмы?
Но если это так, то что мешает попробовать разработать синтаксис языка программирования, в котором можно было бы использовать сразу императивный и декларативный стили программирования одновременно?
Что мешает совместить императивный и декларативный стили написания в рамках одной программы?
В любой компьютерной программе всегда существует разделение кода на описание данных и на языковые конструкции для их обработки, т.е. фактически это разделение на данные и на функции. Мне кажется, что основная сложность, которая не дает использовать разные стили программирования в рамках одного языка, это необходимость разделять описываемые сущности на «данные» и «управляющие конструкции». Ведь это свойство (необходимость разделения сущностей на «данные» и «функции»), является неотъемлемой частью любого языка программирования.
Это не удивительно, ведь на заре становления ИТ индустрии, создатели первых языков программирования ориентировались исключительно на императивный стиль, потому что назначением любого компилятора было — преобразовать исходный текст программы в машинные инструкции. И на примерах модульного, структурного и объекто-ориентированного подходах показана и доказана необходимость оформления исполняемого кода в выделенные процедуры с последующей их группировкой в модули и классы.
А декларативный стиль написания программ стал появляться только после создания высокоуровневых языков программирования. И основная цель создания данных языков сместилась на поиск решения конечного пользователя, а не на упрощение генерации бинарных файлов с машинными инструкциями. Если внимательно посмотреть на приведенные выше примеры кода, то можно заметить, что в них определения данных и операторы для их обработки идут вперемешку, (для примера на С++ это определение лямбд функции), что кардинально отличается от императивного подхода.
Так может быть, основная особенность декларативного стиля как раз и заключается в том, что в нем не разделяются «данные» и «действия над данными»? Или как вариант, можно не указывать выполняемые действия над данными вовсе (как в некоторых SQL конструкциях)?
Может быть как раз именно эта особенность (возможность последовательной записи программного кода в соответствии с собственными логическими рассуждениями, в которых могут перемежаться «данные» и «функции», как это происходит при человеческом мыслительном процессе), не дает в полной мере реализовать возможность совмещения императивных и декларативных стилей программирования?
И если это так, тогда можно попробовать разработать такой синтаксис, который будет поддерживать определение как данных, так и декларацию функций в рамках единого потока языковых конструкций!
Проверка гипотезы в новом языке программирования
Для поверки данного предположения, я решил добавить в свой новый язык программирования (синтаксис которого допускает определение функций в одном потоке с описанием обрабатываемых данных), недостающие алгоритмические конструкции, которые позволяли бы реализовать императивный стиль программирования, несмотря на изначальную ориентацию только на декларативную парадигму.
Правда с учетом изначальных ограничений синтаксиса нового языка (запрет на применение операторов в виде зарезервированных ключевых слов), в качестве оператора проверки условия была выбрана синтаксическая конструкция, соответствующая по смыслу термину «следует», т.е. тире и угловая скобка «->».
В результате, условный оператор получился практически математический, который легко объединяется в последовательности для реализации проверки множественных условий вида «else if». А для объединения сразу нескольких логического операторов между собой и для отделения их от последующего действия, операторы проверки условия можно заключать в круглые скобки.
В общем случае условный оператор в новом языке программирования имеет вид:
Или расширенный вариант, для наглядности записанный с отступами:
Тогда и операторы циклов можно будет записать практически аналогично, только выбрав в качестве оператора, не оператор следования, а следование с возвратом (раз уж нужно указывать циклическое действие).
счетный цикл для работы с итератором*:
*) Разъяснение операторов «итератор» приводится в этой статье
В этом случае, решение тестового примера декларативной программы на Прологе можно будет легко оформить, как в императивном, так и в декларативном стилях одновременно!
Примерно это и требовалось показать!
Я почти закончил эксперименты с синтаксисом языка. И теперь, после добавления в него недостающих алгоритмический конструкций (ветвления и циклов), планирую, что следующая статья о новом языке программирования будет посвящена описанию его полного синтаксиса и публикации исходников прототипа компилятора для возможности поиграться на реальных примерах.
Ну и пять копеечек про ООП. Как ни странно:
1) В нем таки есть декларативная часть, все эти pubic, private, virtual, etc
2) Эта декларативная часть, внезапно, элегантно позволяет реализовывать декомпозицию и управление сложностью.
3) При наличии перегрузок, можно поиграть в алгебраическое мышление, рассматривая взаимодействие двух объектов как бинарную операцию.
Ух ты, ООП — это обкатанная практикой технология, которая совмещает декларатив с императивом и вроде позволяет освоившим ее вкусно кушать и мягко спать!
Вопрос №11197 от пользователя Vyacheslav Makarov в уроке «Переменные, декларативное и императивное программирование», курс «Введение в программирование»
Возникли проблемы с тестом. Думаю, не я один тут застрял. На моё усмотрение к императивному тут явно относится только рекурсия и изменение состояния. Но какие варианты не нажимал, всё неправильно. Могли бы вы разъяснить как отличить императивное от декларативного в данной ситуации? Извиняюсь за тупой вопрос, если что))
Выберите концепции, имеющие прямое отношение только к императивному программированию (нужно выбрать все правильные ответы)
На моё усмотрение к императивному тут явно относится только рекурсия и изменение состояния
Попробуйте ещё раз вернуться в теорию где рассматривается пример с факториалом. Я напомню, что рекурсивная функция – декларативна. А что используется в итеративной функции факториала? Ответив на этот вопрос вы скорее всего пройдёте этот тест. Пишите, если будут вопросы.
Вы правы про изменение состояния. Но почему рекурсия? У этого вопроса два правильных ответа, и второй ответ связан с первым — с изменением состояния. В каком из пунктов происходит изменения состояния?
То есть, лучше задаваться вопросом «какие изменения происходят?», а не «как»? И почему в рекурсии не происходит никаких изменений, если мы в ней меняем значение переменной? Или о каком состоянии идет речь?
Рекусрия это отложенные вычисления, а не изменения переменных. Переменные применяются непосредственно при вызове функции, дальше они не меняются, а происходит предсказуемая обработка данных без повторения частей программы и использования циклов. Советую вам почитать ту же википедию на эту тему, ну либо просто двигаться дальше по курсам профессии, где будет много задач требующих реализацию рекурсивных функций.
Спасибо за пояснение.
Я тоже не понял вопроса )
Суть в том, что декларативный и императивный подходы отличаются в способе достижения цели. Для императивного подхода необходимо задать четкие указания, как выполнить ту или иную задачу. К примеру: «чтобы приготовить пирог, мне нужно купить продукты». Декларативный: «приготовь пирог». Т.е. в декларативном подходе мы не опираемся на инструкции необходимые для реализации задачи, нам просто нужно её выполнить, не задумываясь о её деталях. Для примера (очень хорошего) можешь ознакомиться с элементами html и поймешь разницу. Сам долго разбирался, так как при декларативном подходе зачастую можно встретить нотку императивного и тут уже нужно мозговать:) Та же рекурсия имеет описание что ей необходимо сделать (как бы императивность есть), но в то же время это описание уже является желаемым результатом, чего не скажешь о сортировке, в которой много инструкций по достижению результата.
Императивное и декларативное программирование простым языком — объясняют эксперты
Авторизуйтесь
Императивное и декларативное программирование простым языком — объясняют эксперты
Начинающему программисту несложно запутаться в различных терминах — взять только объектно-ориентированное, динамическое, императивное и декларативное программирование. Спросили у экспертов, что из себя представляют два последних подхода.
технический директор центра инновационных технологий и решений «Инфосистемы Джет»
Императивные языки, такие, как 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). Однако практически все современные языки программирования общего назначения высокого уровня, за исключением некоторых функциональных, относятся к императивным языкам.
Выбор той или иной парадигмы программирования — императивной или функциональной — определяется, главным образом, требованиями к программе и набором достоинств и недостатков каждой из парадигм. Так, например, независимость функций по данным в функциональных языках и отсутствие побочных эффектов чрезвычайно сокращает количество ошибок и позволяет эффективно распараллеливать код. Поэтому для создания высоконагруженных систем с высоким уровнем параллельных вычислений более оправданно выбирать один из функциональных языков.
С другой стороны, неизменность входных данных в функциональных языках программирования затрудняет создание систем, активно выполняющих ввод-вывод и модификацию уже имеющихся данных. Для реализации таких систем предпочтительнее выбирать императивные языки.
руководитель отдела разработки ECM «Техносерв Консалтинг»
Императивное программирование — это парадигма, основанная на составлении алгоритма действий (инструкций/команд), которые изменяют состояние (информацию/данные/память) программы. Первыми языками программирования, основанными на таком подходе, были машинные коды и ассемблеры. Фактически, программа на этих языках — это код, который выполняется компьютером сразу, без предварительной компиляции. Из языков высокого уровня, требующих компиляции исходного кода программы в машинный код (или интерпретации), к императивным можно отнести C, C++, Java.
Рассмотрим ещё один пример. Допустим, мы хотим приготовить обед.
В императивной парадигме это выглядит как-то так:
В декларативной: хочу на обед жареное мясо с овощами (неплохо звучит, правда? :)).
Вроде бы различия очевидны. Однако, императивный язык не мешает обобщить и автоматизировать отдельные задачи. Можно реализовать некий «слой» кода, библиотеки, которые будут «уметь» выполнять отдельные этапы алгоритма: определять по рецепту, есть ли в наличии необходимые продукты, заказывать их доставку, пользоваться плитой и т.д. Получится, что программный код императивного языка программирования, использующий такие библиотеки, уже не будет по своей структуре так уж сильно отличаться от декларативного.
На практике, при написании кода и выбора подхода, разработчик отталкивается не только от возможностей и ограничений языка программирования, но и удобства использования той или иной парадигмы в данном конкретном случае.





