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

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

  • автор:

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

Функциональное программирование представляет одну из парадигм программирования, которая базируется на следующих принципах:

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

  • Функции представляют полноценные объекты («first-class objects», «first-class citizens»). Это означает, что переменным можно присваивать также, как и другие объекты и примитивные значения; функции можно использовать в качестве аргументов других функций или в качестве их возвращаемого значения.
  • Функции работают с неизменяемыми структурами данных. Структуры данных в функциональном программировании обычно неизменяемы или не изменяются. Операции же, которые выполняются над структурами данных, при необходимости создают новые структуры данных и возвращают их в качестве результатов. Например, в чисто функциональных языках программирования после создания списков или других структур данных их нельзя изменить
  • Функции не имеют побочных эффектов. В функциональном программировании функции обычно вообще не имеют побочных эффектов и ведут себя скорее как математические функции. Это означает, что функции в функциональном программировании всегда возвращают один и тот же результат для одних и тех же входных данных и не вызывают никаких побочных эффектов. В чисто функциональных языках побочные эффекты уже предотвращаются самим языком.
  • Функциональные программы применяют декларативный стиль программирования. Императивное программирование — это парадигма программирования, в которой компьютеру даются очень точные индивидуальные инструкции о том, как решить проблему. В императивных программах используются явные операторы цикла (циклы while, for и т. д.), условные операторы (if-else) и их последовательности. Функциональные же программы исповедуют декларативный стиль, что означает, что программа больше говорит, что следует делать, а не как . В результате функциональные программы обычно более читабельны, более содержательны и более компактны, чем эквивалентный императивный код.

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

    // набор объектов для перебора const people = [ , , , , ]; // перебор объектов массива for(let i = 0; i

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

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

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

    // набор объектов для перебора const people = [ , , , , ]; const printName = (p)=>console.log(p.name); people.forEach(printName);

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

    Парадигмы императивного и функционального программирования: взвешенное сравнение

    Эй! GPT-4 помог мне составить эту статью.

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

    Императив: пошаговое руководство вашим кодом

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

    Плюсы:

    1. Контроль: императивное программирование предлагает детальный контроль, что делает его отличным выбором для разработчиков, которым необходимо манипулировать структурами данных или управлять ресурсами.
    2. Знакомство: большинство разработчиков знакомятся с программированием через императивные языки, такие как C, Java или Python, что делает эту парадигму более знакомой и доступной.

    Минусы:

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

    Декларативный: описание желаемого результата

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

    Плюсы:

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

    Минусы:

    1. Ограниченный контроль. Декларативное программирование может не обеспечивать уровень контроля, необходимый некоторым разработчикам, особенно при работе с низкоуровневыми задачами или приложениями, критически важными для производительности.
    2. Кривая обучения: разработчики, привыкшие к императивным языкам, могут поначалу найти концепции декларативного программирования незнакомыми и сложными для понимания.

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

    Функциональный: обтекаемый и математический

    Функциональное программирование, основанное на математических концепциях, рассматривает вычисления как оценку математических функций. Он способствует неизменяемости, функциям высшего порядка, рекурсии и избеганию общего состояния, что приводит к более чистому и предсказуемому коду. Примеры функциональных языков включают Haskell, Lisp и ML.

    Плюсы:

    1. Удобочитаемость: функциональный код, как правило, более лаконичен и его легче анализировать, поскольку функции предназначены для выполнения одной функции.
    2. Параллелизм. Акцент функционального программирования на неизменность и отсутствие побочных эффектов делает его естественным подходом для параллельного и параллельного программирования.

    Минусы:

    1. Кривая обучения: для разработчиков, привыкших к императивным языкам, функциональное программирование может потребовать значительного изменения мышления и адаптации к новым концепциям.
    2. Производительность: в некоторых случаях функциональное программирование может привести к менее производительному коду из-за более высокого использования памяти или вычислительных накладных расходов.

    Объектно-ориентированное программирование (ООП): моделирование объектов реального мира

    ООП — это парадигма программирования, которая использует объекты, представляющие объекты реального мира, для организации и структурирования кода. С помощью ООП вы можете создавать классы для определения объектов и их методов поведения (функций). ООП способствует повторному использованию кода и модульности за счет наследования, инкапсуляции и полиморфизма. Примеры языков ООП включают Java, C++ и Python.

    Плюсы:

    1. Модульность: ООП поощряет разделение задач и модульную структуру, упрощая поддержку и расширение кода.
    2. Повторное использование кода: используя наследование и композицию, ООП позволяет повторно использовать код, уменьшая избыточность и улучшая ремонтопригодность.

    Минусы:

    1. Сложность: ООП может усложнить как дизайн, так и реализацию, что может привести к трудностям в понимании и сопровождении кода.
    2. Негибкость: в некоторых случаях ООП может привести к негибким проектам, которые трудно рефакторировать, особенно при работе с глубокими иерархиями наследования или тесно связанными компонентами.

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

    Поиск подходящего варианта: взвешенная точка зрения

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

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

    Помните, что в мире программирования нет универсального решения! Примите разнообразие парадигм и позвольте вашему коду процветать.

    Давайте рассмотрим некоторые вопросы, которые могут быть на экзамене!

    Раздел 1: Введение в парадигмы программирования

    1.1 Императив против Функциональности:

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

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

    Раздел 2. Параллелизм

    2.1 Проблемы разработки языковой поддержки параллелизма:

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

    Можете ли вы перечислить две общие конструкции, используемые для создания потоков и управления ими в популярных языках программирования?

    2.2 Необходимость синхронизации:

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

    Почему важна синхронизация при работе с общими ресурсами в параллельном программировании?

    2.3 Модель параллелизма с передачей сообщений:

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

    В чем преимущество использования модели параллелизма с передачей сообщений по сравнению с моделью с общей памятью?

    2.4 Потоки/многопоточность и методы асинхронного программирования:

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

    Напишите простой пример использования потоков на выбранном вами языке программирования.

    Раздел 3: Функциональная парадигма

    3.1 Функции высшего порядка:

    Функции высшего порядка — это функции, которые могут принимать другие функции в качестве аргументов или возвращать их в качестве результатов.

    Каков практический вариант использования функций высшего порядка в функциональном программировании?

    3.2 Неизменяемость:

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

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

    3.3 Рекурсия:

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

    Напишите рекурсивную функцию, вычисляющую n-е число Фибоначчи, на любом функциональном языке программирования по вашему выбору.

    3.4 Лямбда/лямбда-выражения:

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

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

    Раздел 4: Логическая парадигма

    4.1 Унификация:

    Унификация — это процесс в языках логического программирования, таких как Пролог, где два термина становятся равными путем нахождения замены, которая делает их идентичными.

    Почему унификация важна для языков логического программирования, таких как Пролог?

    4.2 Возврат:

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

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

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

    Объяснение популярных парадигм программирования

    Знание этих стилей парадигмы поможет вам более эффективно читать и писать код. Вот введение с примерами и терминологией.

    Если вас интересует какая-то конкретная парадигма, например объектно-ориентированное программирование (ООП), не стесняйтесь переходить к этому разделу для ознакомления.

    1. Введение. Что такое «парадигма программирования»?

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

    Парадигмы программирования часто используются для:

    1. Описывать способы организации кода
    2. Помогите классифицировать языки программирования на основе их особенностей

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

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

    2. Императивное и декларативное программирование

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

    Императивное программирование использует операторы, которые описывают, как изменять состояние программы шаг за шагом. Вот пример императивного программирования:

    1| var name = "Lea" 2| var greeting = "Hi!"; 3| var message = name + " says " + greeting; 4| // Result: The message is "Lea says Hi!"
    • Этот код JavaScript показывает серию операторов, которые изменяют состояние программы.
    • Поток управления часто достигается с помощью условного ветвления (например, if , else или switch ) и циклов (например, for или while ).
    • Императивное программирование является старейшим и наиболее близким к машинному языку, поэтому многие низкоуровневые языки программирования, такие как ассемблер, считаются императивными языками программирования.

    Декларативное программирование описывает какое назначение программы, а не описывает, как это сделать. Вот пример декларативного программирования:

    1| SELECT name, greeting 2| FROM people_table 3| WHERE name='Lea' 4| -- Result: Lea | Hi!
    • Этот код SQL описывает то, что мы хотим, а не то, как это получить
    • Декларативное программирование иногда используется как общий термин для описания кода, который не является императивным.
    • Другие примеры декларативных языков включают HTML, CSS и регулярные выражения.

    3. Процедурное программирование

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

    Вот пример процедурного программирования:

    1| var message = ""; 2| function updateMessage(person, greeting) < 3| message = person + " says " + greeting; 4| >5| 6| updateMessage("Lea", "Hi!"); 7| // Result: The message is "Lea says Hi!"

    Сначала мы определяем переменную с именем message и процедуру ее изменения с именем updateMessage (определяется с помощью ключевого слова function , поскольку это JavaScript). Процедура updateMessage вызывается (активируется) в строке 6; он принимает два текстовых аргумента («Леа» и «Привет!»), назначает их своим переменным параметрам ( person и greeting ) в зависимости от порядка, а затем изменяет значение message на «Леа говорит привет! ». Мы могли бы снова вызвать эту процедуру с другими аргументами, чтобы изменить сообщение во второй раз, например так:

    8| updateMessage("Lukas", "Hey"); 9| // Result: Now the message changed to "Lukas says Hey"

    Процедуры и функции также могут возвращать вычисленное значение, например:

    9| function getMessage(person, greeting) < 10| return person + " says " greeting; 11| >12| newMessage = getMessage("Emilie", "Bonjour!") 13| // Result: newMessage is "Emilie says Bonjour!"

    Общая терминология процедурного программирования:

    • Процедура/подпрограмма: группа кодов, которые можно активировать дистанционно
    • Call: означает активировать процедуру
    • Параметры: переменные, которые определяют, какие входные данные могут быть переданы процедуре.
    • Аргументы: входные данные, предоставленные процедуре и назначенные ее параметрам.
    • return : общее ключевое слово для определения вывода процедуры или функции.
    Ключевые выводы для процедурного программирования

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

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

    4. Объектно-ориентированное программирование (ООП)

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

    Структуры данных Class и Object являются краеугольным камнем объектно-ориентированного программирования. Класс — это план, из которого вы можете создавать объекты. Например, если у вас есть план класса под названием «Человек», вы можете создать разных людей, таких как Леа и Лукас, из плана «Человек».

    Вот пример объектно-ориентированного программирования (ООП) с использованием JavaScript. В этом примере наша цель — заставить человека по имени Леа сказать «Привет!». Начнем с определения класса (схемы) с именем Person . Класс Person будет содержать конструктор, два свойства и метод:

    • Человек конструктор (используется для создания объекта из класса): constructor
    • Свойства человека (атрибуты, хранящиеся как переменные): name и greeting
    • Человек метод (поведение хранится как функция): greet()
    1| class Person < 2| constructor(name, greeting) < 3| this.name = name; 4| this.greeting = greeting; 5| >6| greet() < 7| console.log(this.name + " says " + this.greeting); 8| >9| >

    Далее мы можем создать объект «Lea» из нашего класса Person :

    10| var leaObject = new Person("Lea", "Hi!");

    Строка 10 создает объект Lea, используя класс Person в качестве схемы. Из-за того, как написан метод constructor класса, первый аргумент («Lea») назначается свойству name , а второй аргумент («Привет!») назначается свойству greeting .

    11| leaObject.greet(); 12| // Result: "Lea says Hi!"

    Строка 11 вызывает функцию greet() leaObject , чтобы Леа сказала «Привет!». Обратите внимание на нотацию «точка»: точка ( . ) — это общий синтаксис, используемый для доступа к свойству или методу внутри предыдущего объекта. В данном случае мы вызываем метод greet , функцию, являющуюся частью leaObject .

    Мы также можем использовать класс Person , чтобы легко создавать больше людей, каждый из которых имеет свой собственный встроенный метод greet() :

    13| var lukasObject = new Person("Lukas", "Hallo!"); 14| var emilieObject = new Person("Emilie", "Bonjour!"); 15| 16| lukasObject.greet(); // Result: "Lukas says Hallo!" 17| emilieObject.greet(); // Result: "Emilie says Bonjour!"

    Другой распространенной, но более сложной функцией ООП является наследование. Вы также можете использовать наследование для создания класса на основе другого класса. Ниже мы создаем более конкретный тип Person, называемый Player, который имеет все те же свойства и методы, что и Person, но также некоторые дополнительные:

    18| class Player extends Person < 19| constructor(name, greeting, score) < 20| super(name, greeting); 21| this.score = score; 22| >23| sayScore() < 24| console.log(this.name + "'s score is " + this.score) 25| >26| > 27| 28| const tobyObject = new Player("Toby", "Hello", 90); 29| 30| tobyObject.greet(); // Result: "Toby says Hello" 31| tobyObject.sayScore(); // Result: "Toby's score is 90"

    Поскольку класс Player расширяет класс Person , новый tobyObject имеет свойства и методы как Person, так и Player. Оператор super в строке 20 предоставляет их из класса Person . Наследование упрощает написание новых классов и помогает организовать взаимосвязь между ними интуитивно понятным способом.

    Общая терминология ООП:

    • Класс: схема создания логических групп кода
    • Объект: объект, созданный из плана класса.
    • Свойство: переменная, являющаяся частью класса или объекта, которая описывает аспект или состояние объекта.
    • Метод: функция, которая является частью класса или объекта и позволяет ему действовать
    • constructor : общий термин и ключевое слово для специального метода, используемого только для создания объектов из класса.
    • this или self : общие ключевые слова для обозначения других частей того же объекта изнутри.
    • Инкапсуляция: свойства и методы могут иметь такие обозначения, как общедоступные или частные, чтобы более тщательно контролировать использование класса.
    • Абстракция: лучше всего сделать классы простыми в использовании, предоставляя только простые общедоступные методы класса, которые скрывают более сложную функциональность.
    • Наследование: новый класс может быть создан на основе другого класса; новый производный класс наследует все свойства и методы базового класса
    • Полиморфизм: метод может менять функциональность в зависимости от ситуации; например, если у объекта Person есть метод getName() , они могут сказать «Lea», но если объект Player наследует и изменяет функциональность класса Person, их метод getName() может возвращать онлайн-имя, например «Lea_042».
    Основные выводы по объектно-ориентированному программированию (ООП)

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

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

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

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

    Вот пример, демонстрирующий некоторые концепции функционального программирования:

    1| function makeIntro(name) < 2| return name + " says "; 3| >4| function makeGreeting(makeIntroFunction, name, message) < 5| return makeIntroFunction(name) + message; 6| >7| 8| console.log(makeGreeting(makeIntro, "Lea", "Hi!")); 9| // Result: "Lea says Hi!"

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

    В строке 8 происходит все действие. Поскольку все функции являются чистыми, меньше шансов, что внешнее изменение неожиданно изменит результат этого кода. Это одно из преимуществ функционального программирования. Код также достаточно модульный. Например, в настоящее время результатом является «Леа говорит привет!», но если вы хотите изменить стиль вступления, вы можете просто указать другую функцию makeIntro для makeGreeting и вместо этого получить что-то другое, например «[Леа]: Привет!».

    Общая терминология функционального программирования:

    • Функция: процедура, которая выполняет задачу и возвращает результат
    • первоклассные функции: когда функции рассматриваются как любая другая переменная; их можно изменять, назначать переменным и даже передавать и возвращать через другие функции (известные как функции более высокого порядка).
    • Чистая функция: функция, которая 1) использует только данные, предоставленные через ее параметры, обеспечивая согласованность (без скрытых входных данных, таких как глобальные переменные), и 2) не имеет побочных эффектов. (нет скрытых выходов)
    • Побочные эффекты: когда функция изменяет или мутирует что-либо вне области действия функции, в обход инструкции return (например, скрытые выходные данные, такие как изменяемые данные и вызовы процедурных функций)
    Основные выводы по функциональному программированию

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

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

    6. Заключительные замечания

    • Мультипарадигмальные языки программирования, такие как JavaScript и Python, поддерживают использование всех пяти популярных парадигм программирования, и часто хорошие программные решения используют комбинацию этих стилей. Например, иногда использование чистых функций для методов класса ООП может сделать код более проверяемым и понятным.
    • Пять парадигм программирования, обсуждаемые в этой статье, в настоящее время являются наиболее используемыми и обсуждаемыми, но есть и другие, включая логическое программирование и структурированное программирование.
    • Попробуйте применить концепции из этих парадигм, чтобы написать более стабильный, понятный и пригодный для повторного использования код.
    • Удачного кодирования!

    Дополнительная литература:

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

    Парадигмы программирования

    Моё кунг-фу сильнее твоего кунг-фу (из разговора программистов).

    Время чтения: 11 мин

    Открыть/закрыть навигацию по статье

    1. Кратко
    2. Стили объяснения
      1. Императивный стиль
      2. Декларативный стиль
      3. Разница между подходами
      1. Процедурное программирование
      2. Объектно-ориентированное программирование
      1. Логическое программирование
      2. Функциональное программирование
      1. Саша Беспоясов советует

      Обновлено 5 апреля 2023

      Кратко

      Скопировать ссылку «Кратко» Скопировано

      Программировать можно по-разному. Набор приёмов и понятий, которые определяют «как писать», называют парадигмой.

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

      Программа — это инструкция. Когда вы объясняете другу, как к вам доехать, вы, в принципе, программируете.

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

      «Выходи на Александра Невского, сверни налево и иди до перекрёстка, там перейдёшь дорогу, свернёшь налево и пройдёшь до 38 дома, обойди дом, так как вход со двора, дойди до 2 подъезда слева и набери 2468, поднимись на четвёртый этаж, тридцать третья квартира.

      «Адрес: ул. Свободы, д. 38, кв. 33, домофон 2468.»

      Обе инструкции — об одном и том же, но в них есть одно значительное отличие.

      Стили объяснения

      Скопировать ссылку «Стили объяснения» Скопировано

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

      Такой стиль объяснения (или программирования) называется императивным.

      Вторая же инструкция объясняет, что мы должны получить в результате — где друг должен оказаться. Нам не особо важно, как он или она туда доберётся, важно — куда.

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

      Императивный стиль

      Скопировать ссылку «Императивный стиль» Скопировано

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

       function onlyOdd(array)  const result = [] for (const element of array)  if (element % 2 !== 0)  result.push(element) > > return result> function onlyOdd(array)  const result = [] for (const element of array)  if (element % 2 !== 0)  result.push(element) > > return result >      

      Заметьте, как мы строим эту функцию. Мы как бы говорим:

      • сперва присвой переменной result значение [ ] ;
      • затем пройдись по каждому элементу в массиве array ;
        • проверь, что значение этого элемента нечётное;
        • если это так, добавь этот элемент в result ;

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

        Декларативный стиль

        Скопировать ссылку «Декларативный стиль» Скопировано

        Теперь попробуем ту же функцию написать декларативно, то есть не вдаваясь в детали:

         function onlyOdd(array)  return array.filter((element) => element % 2 !== 0)> function onlyOdd(array)  return array.filter((element) => element % 2 !== 0) >      

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

        Разница между подходами

        Скопировать ссылку «Разница между подходами» Скопировано

        Разница между этими подходами — в деталях реализации. В первом случае детали описываем мы сами, во втором они от нас скрыты.

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

        Человеческий мозг может держать одновременно в памяти ограниченное количество объектов. Если мы работаем с какой-то сложной системой, нам будет трудно одновременно помнить и о том, что она делает, и о том, как она устроена.

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

        Плюсы и минусы зависят от контекста:

        • Если вам нужно детально описать какое-то действие — например при разработке конкретного алгоритма — то больше подходит императивный подход;
        • Если вы работаете на уровне бизнес-логики, то лучше писать декларативно, а детали реализации скрыть в более низком уровне абстракций.

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

        Скопировать ссылку «Парадигмы императивного стиля» Скопировано

        Стили программирования развивались много лет, и за это время в каждом появились практики и приёмы, которые со временем стали парадигмами.

        Процедурное программирование

        Скопировать ссылку «Процедурное программирование» Скопировано

        Функция only Odd ( ) , которую мы написали ранее в императивном стиле, как раз похожа на одну из парадигм — процедурное программирование.

        Это парадигма, в которой последовательные команды собираются в подпрограммы.

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

         /* Как пример мы можем рассмотреть программу, которая использует «подпрограммы» (в нашем случае функции), меняя состояние памяти (в нашем случае простой массив битов). Состояние памяти потом может быть использовано, например, для работы с каким-то устройством. */ let memory = [0, 0, 0, 0, 0, 0, 0, 0] function invertSmallestBit()  memory[7] = Number(!memory[7]) return memory> function invertBiggestBit()  memory[0] = Number(!memory[0]) return memory> invertSmallestBit()// [0, 0, 0, 0, 0, 0, 0, 1] invertBiggestBit()// [1, 0, 0, 0, 0, 0, 0, 1] invertSmallestBit()// [1, 0, 0, 0, 0, 0, 0, 0] /* Как пример мы можем рассмотреть программу, которая использует «подпрограммы» (в нашем случае функции), меняя состояние памяти (в нашем случае простой массив битов). Состояние памяти потом может быть использовано, например, для работы с каким-то устройством. */ let memory = [0, 0, 0, 0, 0, 0, 0, 0] function invertSmallestBit()  memory[7] = Number(!memory[7]) return memory > function invertBiggestBit()  memory[0] = Number(!memory[0]) return memory > invertSmallestBit() // [0, 0, 0, 0, 0, 0, 0, 1] invertBiggestBit() // [1, 0, 0, 0, 0, 0, 0, 1] invertSmallestBit() // [1, 0, 0, 0, 0, 0, 0, 0]      

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

        Объектно-ориентированное программирование

        Скопировать ссылку «Объектно-ориентированное программирование» Скопировано

        Самая популярная парадигма императивного стиля — ООП. Это настолько большая тема, что мы собрали о ней отдельный лонгрид.

        ООП (объектно-ориентированное программирование) — парадигма, в которой сущности в программе представляются в виде объектов.

        Каждый объект — экземпляр какого-то класса, некой абстрактной сущности, в которой описано поведение.

         /* Основные понятия ООП: классы и экземпляры классов. Класс можно воспринимать как чертёж, по которому создаются объекты. Экземпляр класса — это созданный по чертежу объект. */ /* Класс User описывает, какие поля (name, admin) и методы (isAdmin, nameOf) будет содержать созданный по этому классу объект. */ class User  constructor(name)  this.name = name this.admin = false > isAdmin()  return this.admin > nameOf()  return this.name >> /* Объекты создаются с помощью new. Свежесозданный объект содержит всё, что было описано в классе User. */ const user = new User('Alex')console.log(user.isAdmin())// falseconsole.log(user.nameOf())// 'Alex' /* Плюс классов в том, что они позволяют единожды описать все одинаковые поля и методы, которые должны быть у однотипных объектов. */ const anotherUser = new User('Alice')console.log(anotherUser.isAdmin())// falseconsole.log(anotherUser.nameOf())// 'Alice' /* Основные понятия ООП: классы и экземпляры классов. Класс можно воспринимать как чертёж, по которому создаются объекты. Экземпляр класса — это созданный по чертежу объект. */ /* Класс User описывает, какие поля (name, admin) и методы (isAdmin, nameOf) будет содержать созданный по этому классу объект. */ class User  constructor(name)  this.name = name this.admin = false > isAdmin()  return this.admin > nameOf()  return this.name > > /* Объекты создаются с помощью new. Свежесозданный объект содержит всё, что было описано в классе User. */ const user = new User('Alex') console.log(user.isAdmin()) // false console.log(user.nameOf()) // 'Alex' /* Плюс классов в том, что они позволяют единожды описать все одинаковые поля и методы, которые должны быть у однотипных объектов. */ const anotherUser = new User('Alice') console.log(anotherUser.isAdmin()) // false console.log(anotherUser.nameOf()) // 'Alice'      

        ООП характеризуется 4 основными аспектами:

        • Абстракцией — выделением таких характеристик объекта, которые достаточно точно описывают его поведение, но не вдаются в детали;
        • Инкапсуляцией — размещением данных внутри того объекта, который их использует;
        • Полиморфизмом — умением работать с разными типами объектов или данных;
        • Наследованием — умением объекта «забирать по наследству» свойства или характеристики от объектов-родителей.

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

        Плюсы ООП

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

        Большая часть энтерпрайз-инструментов для моделирования и документации (UML, DFD, IDEF, Entity-relations) основана именно на объектно-ориентированном представлении систем.

        Ещё один плюс — это изученность подхода. ООП достаточно старо, и о нём написано очень много книг и рекомендаций, а минусы хорошо изучены. Поэтому и велосипедов писать по ходу написания приложения не надо.

        Минусы ООП

        Один из принципов ООП — это инкапсуляция, из-за которой доступ к данным может быть ограничен. Если мы хотим этими данными поделиться, то может случиться, что доступ к ним хотят получить сразу несколько объектов.

        С чтением проблем обычно нет, но если какие-то объекты хотят данные изменить, то мы наткнёмся на проблему конкурентных вычислений.

        Представим, что Google Docs спроектированы без учёта этой проблемы. Если два пользователя одновременно правят один и тот же документ, то правки одного могли бы затирать правки другого. Не круто.

        Но это минус не только ООП, а вообще любой парадигмы, в которой есть общая память или общие данные.

        Ещё одна проблема — это наследование. Простое наследование не всегда полностью отражает отношения компонентов.

        Например, чайник с таймером должен наследоваться от чайника или от таймера? Хороший ответ — от того и от другого (a.k.a множественное наследование). Правильный ответ — наследованию стоит предпочесть композицию.

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

        Скопировать ссылку «Парадигмы декларативного стиля» Скопировано

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

        Логическое программирование

        Скопировать ссылку «Логическое программирование» Скопировано

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

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

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

        Для примера нам бы понадобилось тащить сюда математическую модель для него, а это надолго. Поэтому приводить примеры мы, пожалуй, не станем :–)

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

        Скопировать ссылку «Функциональное программирование» Скопировано

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

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

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

        Побочный эффект — это какое-либо изменение внешней среды.

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

         // Эта функция чистая: function double(x)  return x * 2> /* При одинаковых вызовах она всегда возвращает одинаковый результат. */ double(2)// 4double(2)// 4double(2)// 4 // Следующая функция нечистая: let x = 1 function double()  x *= 2 return x> /* Она меняет (или мутирует) переменную x, которая находится снаружи области видимости функции. */ double()// 2double()// 4double()// 8 // Эта функция — тоже нечистая: function double()  return x * 2> /* Она зависит от переменной из области видимости снаружи функции. */ // При одинаковых вызовах ответ может быть разным: x = 1double()// 2 x = 2double()// 4 // Эта функция чистая: function double(x)  return x * 2 > /* При одинаковых вызовах она всегда возвращает одинаковый результат. */ double(2) // 4 double(2) // 4 double(2) // 4 // Следующая функция нечистая: let x = 1 function double()  x *= 2 return x > /* Она меняет (или мутирует) переменную x, которая находится снаружи области видимости функции. */ double() // 2 double() // 4 double() // 8 // Эта функция — тоже нечистая: function double()  return x * 2 > /* Она зависит от переменной из области видимости снаружи функции. */ // При одинаковых вызовах ответ может быть разным: x = 1 double() // 2 x = 2 double() // 4      

        Плюсы ФП

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

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

        Минусы ФП

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

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

        На практике

        Скопировать ссылку «На практике» Скопировано

        Саша Беспоясов советует

        Скопировать ссылку «Саша Беспоясов советует» Скопировано

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

        Выбор парадигмы зависит от многих факторов: договорённости, язык программирования, привычки и прочее. Но один из таких факторов — удобство решения задачи.

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

        Мультипарадигменные языки

        Скопировать ссылку «Мультипарадигменные языки» Скопировано

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

        Мы можем писать на JS в любой парадигме, которой захотим (В ООП, правда, — с небольшими ограничениями и поправками на систему прототипов).

        Давайте попробуем решить одну и ту же задачу в рамках ООП и ФП. Напишем модуль, который будет прибавлять и отнимать значения.

        В стиле ООП

        Скопировать ссылку «В стиле ООП» Скопировано

         class Calculator  // Храним (инкапсулируем) значение // внутри модуля в поле value. constructor(initial = 0)  this.value = initial > // При добавлении увеличиваем value // на указанное количество. add(x)  this.value += x > // При вычитании уменьшаем value // на указанное количество. subtract(x)  this.value -= x > valueOf ()  return this.value >> // Создаём экземпляр класса// с начальным состоянием, равным 0.const calculator = new Calculator() calculator.add(3)console.log(calculator.valueOf())// 3 calculator.add(4)console.log(calculator.valueOf())// 7 calculator.subtract(1)console.log(calculator.valueOf())// 6 class Calculator  // Храним (инкапсулируем) значение // внутри модуля в поле value. constructor(initial = 0)  this.value = initial > // При добавлении увеличиваем value // на указанное количество. add(x)  this.value += x > // При вычитании уменьшаем value // на указанное количество. subtract(x)  this.value -= x > valueOf ()  return this.value > > // Создаём экземпляр класса // с начальным состоянием, равным 0. const calculator = new Calculator() calculator.add(3) console.log(calculator.valueOf()) // 3 calculator.add(4) console.log(calculator.valueOf()) // 7 calculator.subtract(1) console.log(calculator.valueOf()) // 6      

        В стиле ФП

        Скопировать ссылку «В стиле ФП» Скопировано

         // Функция будет принимать 2 параметра,// потому что состояния,// где бы хранилось первое значение, нет.// Функция должна зависеть только от аргументов.function add(a, b)  return a + b> // Здесь точно так же.function subtract(a, b)  return a - b> // Вызывали бы наши функции мы так:console.log(add(0, 3))// 3console.log(add(3, 4))// 7console.log(subtract(7, 1)) // 6 // Или одной строкой:console.log( subtract(add(add(0, 3), 4), 1))// 6 // Функция будет принимать 2 параметра, // потому что состояния, // где бы хранилось первое значение, нет. // Функция должна зависеть только от аргументов. function add(a, b)  return a + b > // Здесь точно так же. function subtract(a, b)  return a - b > // Вызывали бы наши функции мы так: console.log(add(0, 3)) // 3 console.log(add(3, 4)) // 7 console.log(subtract(7, 1)) // 6 // Или одной строкой: console.log( subtract(add(add(0, 3), 4), 1) ) // 6      

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *