September 20, 2023

Декларативные и Императивные языки программирования

Что такое парадигмы и для чего они нужны?

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

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

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

Какие парадигмы существуют?

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

Наиболее популярные парадигмы:

  • ООП (Объектное ориентированное программирование)
  • ФП (Функциональное программирование)
  • Декларативное программирование
  • Императивное программирование

Примеры использования декларативной парадигмы

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

Вот банальный SQL запрос с которым наверное все повсеместно сталкиваются даже если пользуются ORM-Фреймворками:

SELECT * FROM `users` ORDER BY `id` DESC LIMIT 100;

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

В декларативном стиле мы указываем, что хотим сделать, а детали реализации уже заранее заданы, например: "Получить список пользователей в порядке убывания, ограничиваясь 100 пользователями."

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

SELECT users.name, orders.product 
FROM users 
INNER JOIN orders ON users.id = orders.user_id 
WHERE users.country = 'USA' ORDER BY users.name;

Мы хотим получить имена пользователей из США и названия продуктов, которые они заказали, отсортированные по имени пользователя.

В декларативном стиле мы описываем, что мы хотим получить, и какие условия применить (пользователи из США и сортировка по имени). Детали того, как именно выполнить JOIN и выполнить выборку, определяются внутри SQL-запроса, но в самом запросе мы устанавливаем только "что" и "по каким правилам".

Примеры использования императивной парадигмы

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

Вот наш запрос в декларативном подходе:

SELECT * FROM `users` ORDER BY `id` DESC LIMIT 100;

Теперь давайте рассмотрим как бы это выглядело если мы писали всё в императивном стиле:

// Предположим, что у нас есть массив объектов (бд)
// представляющих пользователей 
const users = [ 
 { id: 3, name: 'Пользователь 3', ... },
 { id: 1, name: 'Пользователь 1', ... }, 
 { id: 2, name: 'Пользователь 2', ... },
 // ...
];

for (let i = 0; i < users.length - 1; i++) {
 for (let j = i + 1; j < users.length; j++) {
  if (users[i].id < users[j].id) { 
   // Обмен местами 
   const temp = users[i]; users[i] = users[j]; users[j] = temp;
  } 
 } 
}

// Ограничиваем результат 100 пользователями (декларативный подход)
const limitedUsers = users.slice(0, 100);
// Выводим результат 
console.log(limitedUsers);

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

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

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

// Сортируем пользователей в порядке убывания по id 
users.sort((a, b) => b.id - a.id);

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

  • Оператор - можно воспринимать как DESC или же ASC в языке SQL
  • sort можно воспринимать как ORDER BY
  • b.id - a.id как указатель на то что мы хотим отсортировать

Заключение

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

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

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

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