В чем разница между функциональным и императивным языками программирования?

159

Большинство основных языков, включая языки объектно-ориентированного программирования (ООП), такие как C #, Visual Basic, C ++ и Java, были разработаны, чтобы в первую очередь поддерживать императивное (процедурное) программирование, тогда как языки, подобные Haskell / gofer, являются чисто функциональными. Кто-нибудь может уточнить, в чем разница между этими двумя способами программирования?

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

Свапнил котвал
источник
1
проверьте этот другой [пост] [1]. Это ясно описывает различия. [1]: stackoverflow.com/questions/602444/…
тета

Ответы:

160

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

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

 int total = 0;
 int number1 = 5;
 int number2 = 10;
 int number3 = 15;
 total = number1 + number2 + number3; 

Каждый оператор изменяет состояние программы, от присвоения значений каждой переменной до окончательного добавления этих значений. Используя последовательность из пяти утверждений, программе явно сказано, как сложить числа 5, 10 и 15 вместе.

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

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

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

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

Для людей ООП или императивных языков:

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

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

Минусы:

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

Когда эволюция идет не так, у вас есть проблемы:

  • Добавление новой операции в объектно-ориентированную программу может потребовать редактирования многих определений классов для добавления нового метода
  • Добавление нового вида вещей в функциональную программу может потребовать редактирования многих определений функций для добавления нового случая.
Крис
источник
10
Чистая функция в этом случае является эквивалентом математической функции. Одни и те же входы всегда будут отображаться на одни и те же выходы. У них также отсутствуют какие-либо побочные эффекты (кроме возврата значения или значений), что означает, что компилятор может выполнять некоторые интересные оптимизации, и это облегчает параллельное выполнение функции, так как не с чем бороться.
WorBlux
Итак, правильные пути и лучшие практики составления поддерживаемых и тестируемых oop-приложений, как правило, заключаются в разработке императивного кода с упадочным состоянием ума?
Кемаль Гюльтекин
4
Я не вижу четкой разницы в тексте, где выделены особенности каждой программы. Большая часть описания процедурного программирования может быть заменена текстом императивного программирования и наоборот.
AxeEffect
7
Этот ответ пытается уточнить, что такое функциональное программирование, но даже не удосуживается определить, что такое чистая функция. Я не понимаю, как кто-то может прочитать этот ответ и уйти, чувствуя уверенность в том, что он знает разницу между декларативным и процедурным программированием.
Ринго
230

Вот разница:

Императив:

  • Начало
  • Включите обувь размером 9 1/2.
  • Освободите место в своем кармане, чтобы хранить массив [7] ключей.
  • Положите ключи в комнате для ключей в кармане.
  • Войдите в гараж.
  • Открытый гараж.
  • Введите автомобиль.

... и так далее, и так далее ...

  • Положите молоко в холодильник.
  • Стоп.

Декларативный, из которых функционал является подкатегорией:

  • Молоко - это полезный напиток, если у вас нет проблем с перевариванием лактозы.
  • Обычно молоко хранится в холодильнике.
  • Холодильник - это коробка, в которой вещи хранятся в прохладе.
  • Магазин - это место, где продаются товары.
  • Под «продажей» мы подразумеваем обмен вещей на деньги.
  • Также обмен денег на вещи называется «покупка».

... и так далее, и так далее ...

  • Убедитесь, что у нас есть молоко в холодильнике (когда оно нам нужно - для ленивых функциональных языков).

Описание: на императивных языках вы говорите компьютеру, как изменять биты, байты и слова в его памяти и в каком порядке. В функциональных мы сообщаем компьютеру, что такое вещи, действия и т. Д. Например, мы говорим, что факториал 0 равен 1, а факториал любого другого натурального числа является произведением этого числа и факториала его предшественника. Мы не говорим: чтобы вычислить факториал n, зарезервировать область памяти и сохранить там 1, затем умножить число в этой области памяти на числа от 2 до n и сохранить результат в том же месте и в конце, область памяти будет содержать факториал.

Инго
источник
1
Спасибо. Это отличный способ посмотреть на это.
L-Samuels
5
Мне понравилось ваше объяснение @Igno, но что-то мне до сих пор неясно. В декларативном, даже если вы просто говорите вещи, но все же вам нужно изменить биты и внести изменения в состояния в машине, чтобы продолжить работу правильно. Меня смущает, что Декларативно как-то похоже на процедурное программирование (например, функции C) , и между ними все же есть большая разница. Разве функции C не совпадают с функциями в функциональном программировании (на машинном уровне)?
Феникс
11
@Игно, как и Суброто, я не очень понимаю твое объяснение. Кажется, что то, что вы написали, можно обобщить так: Нужен ответ ... получите ответ. кажется, игнорировать важные биты, которые как. Я не понимаю, как вы можете просто скрыть эту часть от пользователя, в какой-то момент кто-то должен знать, как это было сделано ... вы не можете держать мастера за кулисами навсегда.
Бретт Томас
3
Это далеко не то, что я понимаю под функциональным программированием. Я думал, что функциональное программирование - это удаление скрытых входов и выходов из функций.
Ринго
7
Замысловатое объяснение.
JoeTidee
14

Большинство современных языков в той или иной степени являются как императивными, так и функциональными, но для лучшего понимания функционального программирования лучше всего взять пример чисто функционального языка, такого как Haskell, в отличие от императивного кода на не очень функциональном языке, таком как java / c #. Я считаю, что это всегда легко объяснить на примере, поэтому ниже один.

Функциональное программирование: вычислить факториал n, т.е. n! то есть nx (n-1) x (n-2) x ... x 2 X 1

-- | Haskell comment goes like
-- | below 2 lines is code to calculate factorial and 3rd is it's execution  

factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial 3

-- | for brevity let's call factorial as f; And x => y shows order execution left to right
-- | above executes as := f(3) as 3 x f(2) => f(2) as 2 x f(1) => f(1) as 1 x f(0) => f(0) as 1  
-- | 3 x (2 x (1 x (1)) = 6

Обратите внимание, что Haskel допускает перегрузку функции до уровня значения аргумента. Теперь ниже приведен пример императивного кода в возрастающей степени императивности:

//somewhat functional way
function factorial(n) {
  if(n < 1) {
     return 1;
  }
  return n * factorial(n-1);   
}
factorial(3);

//somewhat more imperative way
function imperativeFactor(n) {
  int f = 1
  for(int i = 1; i <= n; i++) {
     f = f * i
  }
  return f;
}

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

Более поздний пример можно рассматривать примерно как код java / c # lang, а первую часть - как ограничение самого языка в отличие от Haskell для перегрузки функции на значение (ноль), и, следовательно, можно сказать, что это не пуристический функциональный язык, с другой Со стороны можно сказать, что он поддерживает функциональную прогу. в некоторой степени.

Раскрытие: ни один из приведенных выше кодов не протестирован / выполнен, но, надеюсь, должен быть достаточно хорош, чтобы передать концепцию; Также я был бы признателен за комментарии для любой такой коррекции :)

старый монах
источник
1
Не должно ли это быть return n * factorial(n-1);?
Jinawee
@jinawee, спасибо за указание, я исправил это отn * (n-1)
старый монах
10

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

Проблема: я хочу превратить это существо из лошади в жирафа.

  • Удлинить шею
  • Удлинить ноги
  • Применять пятна
  • Дайте существу черный язык
  • Удалить конский хвост

Каждый элемент может быть запущен в любом порядке для получения одинакового результата.

Императивное программирование носит процедурный характер. Государство и порядок важны.

Проблема: я хочу оставить свою машину.

  1. Обратите внимание на начальное состояние ворот гаража
  2. Остановить машину на дороге
  3. Если дверь гаража закрыта, откройте дверь гаража, запомните новое состояние; в противном случае продолжить
  4. Вытащить машину в гараж
  5. Закрыть гаражные ворота

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

Якуб Келлер
источник
Я вижу только разницу в асинхронности против синхронизации.
Владимир Вуканац
@VladimirVukanac async / sync - это механизм, а не форма программирования
Якуб Келлер
2
О, спасибо, я буду исследовать больше об этом. Не могли бы вы так обновить задачу 1, чтобы она была такой же, как и проблема 2 «Я хочу оставить свой автомобиль», но написана функциональным способом программирования? Тогда параллелизм будет исключен.
Владимир Вуканац
6

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

Отсюда следует, что функциональная программа - это просто выражение.

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

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

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

Ромил Павар
источник
3

Императивный стиль программирования практиковался в веб-разработке с 2005 года до 2013 года.

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

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

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

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

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

Итак, наш босс дает нам список направлений, которые мы знаем как рецепт.

Рецепт расскажет нам, как сделать пирог. Один рецепт написан в императивном стиле примерно так:

  1. Смешайте 1 стакан муки
  2. Добавить 1 яйцо
  3. Добавьте 1 стакан сахара
  4. Вылейте смесь в кастрюлю
  5. Поставить сковороду в духовку на 30 минут и 350 градусов по Фаренгейту.

Декларативный рецепт будет делать следующее:

1 стакан муки, 1 яйцо, 1 стакан сахара - начальное состояние

правила

  1. Если все смешано, поместите в кастрюлю.
  2. Если все перемешано, поместите в миску.
  3. Если все в сковороде, поставить в духовку.

Поэтому императивные подходы характеризуются пошаговыми подходами. Вы начинаете с шага 1 и переходите к шагу 2 и так далее.

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

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

Мы берем начальное состояние или исходные ингредиенты и применяем к ним некоторые правила.

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

Таким образом, в декларативном подходе мы должны знать, как правильно структурировать эти правила.

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

С нашим начальным состоянием это не совпадает, потому что мы еще не смешали наши ингредиенты.

Итак, правило 2 гласит: если они не смешаны, смешайте их в миске. Хорошо, да, это правило применяется.

Теперь у нас есть миска смешанных ингредиентов в нашем штате.

Теперь мы снова применяем это новое состояние к нашим правилам.

Итак, правило 1 гласит: если ингредиенты смешаны, поместите их в кастрюлю, хорошо, да, теперь правило 1 действительно применяется, давайте сделаем это.

Теперь у нас есть это новое состояние, где ингредиенты смешиваются и в кастрюле. Правило 1 больше не актуально, правило 2 не применяется.

Правило 3 гласит: если ингредиенты находятся в сковороде, поместите их в духовку, прекрасно, что это правило относится к этому новому состоянию, давайте сделаем это.

И мы получаем вкусный горячий яблочный пирог или что-то еще.

Теперь, если вы похожи на меня, вы можете подумать, почему мы до сих пор не занимаемся императивным программированием. Это имеет смысл.

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

В декларативном подходе мы можем иметь несколько начальных ингредиентов или начальное состояние, например textInput=“”, одну переменную.

Возможно, ввод текста начинается с пустой строки.

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

  1. Если пользователь вводит текст, обновите ввод текста. Ну, сейчас это не относится.

  2. Если шаблон отображается, рассчитайте виджет.

  3. Если textInput обновлен, перерисовать шаблон.

Ну, ничего из этого не применимо, поэтому программа просто будет ждать события.

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

Мы можем обновить это до “abcd”

Таким образом, мы только что обновили наши обновления text и textInput, правило № 2 не применяется, правило № 3 говорит, что если ввод текста является обновлением, которое только что произошло, затем повторно отображаем шаблон, а затем мы возвращаемся к правилу 2, которое говорит, что шаблон отображен , рассчитать виджет, ладно, давайте посчитаем виджет.

В целом, как программисты, мы хотим стремиться к более декларативным проектам программирования.

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

Даниил
источник
2

• императивные языки:

  • Эффективное исполнение

  • Сложная семантика

  • Сложный синтаксис

  • Параллелизм разработан программистом

  • Комплексное тестирование, не имеет ссылочной прозрачности, имеет побочные эффекты

  • Имеет состояние

• Функциональные языки:

  • Простая семантика

  • Простой синтаксис

  • Менее эффективное исполнение

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

  • Простое тестирование, имеет ссылочную прозрачность, не имеет побочных эффектов

  • Не имеет государства
BoraKurucu
источник
1

Я думаю, что можно выразить функциональное программирование в обязательном порядке:

  • Использование большого количества проверок состояния объектов и if... else/ switchоператоров
  • Некоторый механизм тайм-аута / ожидания, чтобы позаботиться об асинхронности

С таким подходом возникают огромные проблемы:

  • Правила / процедуры повторяются
  • Statefulness оставляет шансы на побочные эффекты / ошибки

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

Пример использования: приложения внешнего интерфейса, такие как логика Android, iOS или веб-приложений, вкл. связь с бэкэндом.

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

  • Состояние гонки
  • Сложная комбинация и последовательность событий. Например, пользователь пытается отправить деньги в банковском приложении. Шаг 1) Выполняйте все следующие действия параллельно, только продолжайте, если все хорошо a) Проверьте, все ли у вас дела хорошо (мошенничество, AML) b) Проверьте, достаточно ли у пользователя баланса c) Проверьте, действительно ли получатель действителен и хорош (мошенничество, AML) и т. Д. Шаг 2) выполнить операцию перевода. Шаг 3) Показать обновление на балансе пользователя и / или какое-либо отслеживание. С RxJava, например, код является кратким и разумным. Без этого я могу представить, что было бы много кода, грязного и подверженного ошибкам кода

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

ericn
источник
-1

Я знаю, что этот вопрос старше, и другие уже хорошо его объяснили. Я хотел бы привести пример проблемы, которая объясняет то же самое в простых терминах.

Проблема: написание таблицы 1.

Решение: -

По повелительному стилю: =>

    1*1=1
    1*2=2
    1*3=3
    .
    .
    .
    1*n=n 

По функциональному стилю: =>

    1
    2
    3
    .
    .
    .
    n

Пояснения в императивном стиле мы пишем инструкции более явно и которые можно назвать более упрощенными.

Где, как в функциональном стиле, вещи, которые говорят сами за себя, будут игнорироваться.

Param NC
источник