В чем разница между функциональным и императивным языками программирования?
Большинство основных языков, включая языки объектно-ориентированного программирования (ООП), такие как C#, Visual Basic, C++ и Java, были разработаны в первую очередь для поддержки императивного (процедурного) программирования, в то время как языки, подобные Haskell/gofer, являются чисто функциональными. Кто-нибудь может уточнить, в чем разница между этими двумя способами программирования?
Я знаю, что выбор способа программирования зависит от требований пользователя, но почему рекомендуется изучать функциональные языки программирования?
11 ответов
Определение: императивный язык использует последовательность утверждений, чтобы определить, как достичь определенной цели. Считается, что эти операторы изменяют состояние программы, поскольку каждое из них выполняется по очереди.
Примеры: Java - императивный язык. Например, можно создать программу для добавления серии чисел:
int total = 0;
int number1 = 5;
int number2 = 10;
int number3 = 15;
total = number1 + number2 + number3;
Каждый оператор изменяет состояние программы, от присвоения значений каждой переменной до окончательного добавления этих значений. Используя последовательность из пяти операторов, программе явно сказано, как сложить числа 5, 10 и 15 вместе.
Функциональные языки: парадигма функционального программирования была явно создана для поддержки чисто функционального подхода к решению проблем. Функциональное программирование является формой декларативного программирования.
Преимущества чистых функций. Основная причина реализации функциональных преобразований в виде чистых функций заключается в том, что чистые функции являются составными: то есть автономными и не сохраняющими состояния. Эти характеристики дают ряд преимуществ, в том числе следующие: повышенная читаемость и удобство обслуживания. Это связано с тем, что каждая функция предназначена для выполнения конкретной задачи с учетом своих аргументов. Функция не зависит от какого-либо внешнего состояния.
Более легкое повторное развитие. Поскольку код легче реорганизовать, изменения в дизайне часто проще реализовать. Например, предположим, что вы пишете сложное преобразование, а затем понимаете, что некоторый код повторяется несколько раз в преобразовании. Если вы выполняете рефакторинг с помощью чистого метода, вы можете вызвать его по своему усмотрению, не беспокоясь о побочных эффектах.
Более легкое тестирование и отладка. Поскольку чистые функции легче тестировать изолированно, вы можете написать тестовый код, который вызывает чистую функцию с типичными значениями, допустимыми случаями ребер и недопустимыми случаями ребер.
Для людей ООП или императивных языков:
Объектно-ориентированные языки хороши, когда у вас есть фиксированный набор операций над вещами, и когда ваш код развивается, вы в первую очередь добавляете новые вещи. Это может быть достигнуто путем добавления новых классов, которые реализуют существующие методы, а существующие классы остаются одни.
Функциональные языки хороши, когда у вас есть фиксированный набор вещей, и когда ваш код развивается, вы в первую очередь добавляете новые операции над существующими вещами. Это может быть достигнуто путем добавления новых функций, которые вычисляются с существующими типами данных, а существующие функции остаются в покое.
Минусы:
Выбор способа программирования зависит от требований пользователя, поэтому вред будет только тогда, когда пользователи не выберут правильный путь.
Когда эволюция идет не так, у вас есть проблемы:
- Добавление новой операции в объектно-ориентированную программу может потребовать редактирования многих определений классов для добавления нового метода
- Добавление нового вида вещей в функциональную программу может потребовать редактирования многих определений функций для добавления нового случая.
Вот разница:
Императив:
- Начните
- Включите обувь размером 9 1/2.
- Освободите место в своем кармане, чтобы хранить массив [7] ключей.
- Положите ключи в комнате для ключей в кармане.
- Войдите в гараж.
- Открытый гараж.
- Введите автомобиль.
... и так далее, и так далее...
- Положите молоко в холодильник.
- Стоп.
Декларативный, из которых функционал является подкатегорией:
- Молоко - это полезный напиток, если у вас нет проблем с перевариванием лактозы.
- Обычно молоко хранится в холодильнике.
- Холодильник - это коробка, в которой вещи хранятся в прохладе.
- Магазин - это место, где продаются товары.
- Под "продажей" мы подразумеваем обмен вещей на деньги.
- Также обмен денег на вещи называется "покупка".
... и так далее, и так далее...
- Убедитесь, что у нас есть молоко в холодильнике (когда оно нам нужно - для ленивых функциональных языков).
Описание: на императивных языках вы говорите компьютеру, как изменять биты, байты и слова в его памяти и в каком порядке. В функциональных мы сообщаем компьютеру, что такое вещи, действия и т. Д. Например, мы говорим, что факториал 0 равен 1, а факториал любого другого натурального числа является произведением этого числа и факториала его предшественника. Мы не говорим: чтобы вычислить факториал для n, зарезервировать область памяти и сохранить там 1, затем умножить число в этой области памяти на числа от 2 до n и сохранить результат в том же месте и в конце, область памяти будет содержать факториал.
Большинство современных языков в той или иной степени являются как императивными, так и функциональными, но для лучшего понимания функционального программирования лучше всего взять пример чисто функционального языка, такого как Haskell, в отличие от императивного кода на не очень функциональном языке, таком как java/ C#. Я считаю, что это всегда легко объяснить на примере, поэтому ниже один.
Функциональное программирование: вычислить факториал из n, т.е. n! то есть n x (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 * 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 для перегрузки функции на значение (ноль), и, следовательно, можно сказать, что это не пуристический функциональный язык, с другой Со стороны можно сказать, что он поддерживает функциональную прогу. в некоторой степени.
Раскрытие: ни один из приведенных выше кодов не протестирован / выполнен, но, надеюсь, должен быть достаточно хорош, чтобы передать концепцию; Также я был бы признателен за комментарии для любой такой коррекции:)
Функциональное программирование - это форма декларативного программирования, которая описывает логику вычислений, и порядок выполнения полностью не подчеркивается.
Проблема: я хочу превратить это существо из лошади в жирафа.
- Удлинить шею
- Удлинить ноги
- Применять пятна
- Дайте существу черный язык
- Удалить конский хвост
Каждый элемент может быть запущен в любом порядке для получения одинакового результата.
Императивное программирование носит процедурный характер. Государство и порядок важны.
Проблема: я хочу оставить свою машину.
- Обратите внимание на начальное состояние ворот гаража
- Остановить машину на дороге
- Если дверь гаража закрыта, откройте дверь гаража, запомните новое состояние; в противном случае продолжить
- Вытащить машину в гараж
- Закрыть гаражные ворота
Каждый шаг должен быть сделан, чтобы достичь желаемого результата. Вытягивание в гараж, когда дверь гаража закрыта, приведет к поломке двери гаража.
//The IMPERATIVE way
int a = ...
int b = ...
int c = 0; //1. there is mutable data
c = a+b; //2. statements (our +, our =) are used to update existing data (variable c)
Императивная программа = последовательность операторов, изменяющих существующие данные.
Сосредоточьтесь на ЧТО = наши изменяющиеся данные (изменяемые значения, также известные как переменные).
Чтобы связать императивные утверждения = использовать процедуры (и / или оп).
//The FUNCTIONAL way
const int a = ... //data is always immutable
const int b = ... //data is always immutable
//1. declare pure functions; we use statements to create "new" data (the result of our +), but nothing is ever "changed"
int add(x, y)
{
return x+y;
}
//2. usage = call functions to get new data
const int c = add(a,b); //c can only be assigned (=) once (const)
Функциональная программа = список функций, "объясняющих", как можно получить новые данные.
Сосредоточьтесь на КАК = наша функция
add
.
Чтобы связать функциональные "утверждения" = используйте композицию функций.
Эти фундаментальные различия имеют глубокое значение.
В серьезном программном обеспечении много данных и много кода.
Таким образом, одни и те же данные (переменная) используются в нескольких частях кода.
A. В императивной программе изменчивость этих (общих) данных вызывает проблемы
- код сложно понять / поддерживать (поскольку данные могут быть изменены в разных местах / способами / моментами)
- распараллеливание кода является сложной задачей (только один поток может изменять область памяти одновременно), что означает, что изменяющиеся обращения к одной и той же переменной должны быть сериализованы.
В качестве преимущества: данные изменяются на месте, меньше необходимости копировать.
Б. С другой стороны, функциональный код использует неизменяемые данные, которые не имеют таких проблем. Данные доступны только для чтения, поэтому состояния гонки отсутствуют. Код можно легко распараллелить. Результаты можно кэшировать.
Как недостаток: данные много копируются, чтобы получить "модификации".
Функциональное программирование - это "программирование с помощью функций", когда функция обладает некоторыми ожидаемыми математическими свойствами, включая прозрачность ссылок. Из этих свойств вытекают дополнительные свойства, в частности знакомые этапы рассуждения, допускаемые заменяемостью, которые приводят к математическим доказательствам (т. Е. Оправдывают уверенность в результате).
Отсюда следует, что функциональная программа - это просто выражение.
Вы можете легко увидеть контраст между двумя стилями, отметив места в императивной программе, где выражение больше не является ссылочно-прозрачным (и, следовательно, не построено с функциями и значениями и не может само по себе быть частью функции). Два наиболее очевидных места: мутация (например, переменные), другие побочные эффекты, нелокальный поток управления (например, исключения)
На этой основе программ-выражений, состоящих из функций и значений, строится целая практическая парадигма языков, концепций,"функциональных паттернов", комбинаторов и систем различных типов и алгоритмов оценки.
По наиболее экстремальному определению, почти любой язык, даже C или Java, можно назвать функциональным, но обычно люди резервируют этот термин для языков со специально соответствующими абстракциями (такими как замыкания, неизменяемые значения и синтаксические средства, такие как сопоставление с образцом). Что касается использования функционального программирования, то оно включает использование функций и строит код без каких-либо побочных эффектов. раньше писал доказательства
• Императивные языки:
Эффективное исполнение
Сложная семантика
Сложный синтаксис
Параллелизм разработан программистом
Комплексное тестирование, не имеет ссылочной прозрачности, имеет побочные эффекты
- Имеет состояние
• Функциональные языки:
Простая семантика
Простой синтаксис
Менее эффективное исполнение
Программы можно автоматически сделать параллельными
Простое тестирование, имеет ссылочную прозрачность, не имеет побочных эффектов
- Не имеет государства
Императивный стиль программирования практиковался в веб-разработке с 2005 года вплоть до 2013 года.
С императивным программированием мы выписали код, который перечислял, что именно должно делать наше приложение, шаг за шагом.
Стиль функционального программирования создает абстракцию с помощью умных способов объединения функций.
В ответах упоминается декларативное программирование, и в связи с этим я скажу, что декларативное программирование перечисляет некоторые правила, которым мы должны следовать. Затем мы предоставляем то, что мы называем некоторым начальным состоянием нашего приложения, и мы позволяем этим правилам как-то определять поведение приложения.
Теперь, эти быстрые описания, вероятно, не имеют большого смысла, поэтому давайте пройдемся по различиям между императивным и декларативным программированием, пройдя по аналогии.
Представьте, что мы не создаем программное обеспечение, а вместо этого печем пирожки для жизни. Возможно, мы плохие пекари и не знаем, как испечь вкусный пирог так, как должны.
Итак, наш босс дает нам список направлений, которые мы знаем как рецепт.
Рецепт расскажет нам, как сделать пирог. Один рецепт написан в императивном стиле примерно так:
- Смешайте 1 стакан муки
- Добавить 1 яйцо
- Добавьте 1 стакан сахара
- Вылейте смесь в кастрюлю
- Поставить сковороду в духовку на 30 минут и 350 градусов по Фаренгейту.
Декларативный рецепт будет делать следующее:
1 стакан муки, 1 яйцо, 1 стакан сахара - начальное состояние
правила
- Если все смешано, поместите в кастрюлю.
- Если все перемешано, поместите в миску.
- Если все в сковороде, поставить в духовку.
Поэтому императивные подходы характеризуются пошаговыми подходами. Вы начинаете с шага 1 и переходите к шагу 2 и так далее.
В конечном итоге вы получите конечный продукт. Таким образом, делая этот пирог, мы берем эти ингредиенты, смешиваем их, помещаем в кастрюлю и в духовку, и вы получаете конечный продукт.
В декларативном мире все по-другому. В декларативном рецепте мы должны разделить наш рецепт на две отдельные части, начнем с одной части, которая перечисляет начальное состояние рецепта, как переменные. Итак, наши переменные здесь - это количество наших ингредиентов и их тип.
Мы берем начальное состояние или исходные ингредиенты и применяем к ним некоторые правила.
Поэтому мы берем исходное состояние и пропускаем их через эти правила снова и снова, пока не получим готовый к употреблению клубничный пирог из ревеня или что-то еще.
Таким образом, в декларативном подходе мы должны знать, как правильно структурировать эти правила.
Таким образом, правила, которые мы могли бы хотеть изучить наши ингредиенты или состояния, если смешаны, положить их в кастрюлю.
С нашим начальным состоянием это не совпадает, потому что мы еще не смешали наши ингредиенты.
Итак, правило 2 гласит: если они не смешаны, смешайте их в миске. Хорошо, да, это правило применяется.
Теперь у нас есть миска смешанных ингредиентов в нашем штате.
Теперь мы снова применяем это новое состояние к нашим правилам.
Итак, правило 1 гласит: если ингредиенты смешаны, поместите их в кастрюлю, хорошо, да, теперь правило 1 действительно применяется, давайте сделаем это.
Теперь у нас есть это новое состояние, где ингредиенты смешиваются и в кастрюле. Правило 1 больше не актуально, правило 2 не применяется.
Правило 3 гласит: если ингредиенты находятся в сковороде, поместите их в духовку, прекрасно, что это правило относится к этому новому состоянию, давайте сделаем это.
И мы получаем вкусный горячий яблочный пирог или что-то еще.
Теперь, если вы похожи на меня, вы можете подумать, почему мы до сих пор не занимаемся императивным программированием. Это имеет смысл.
Что ж, для простых потоков да, но большинство веб-приложений имеют более сложные потоки, которые нельзя должным образом отразить при проектировании императивного программирования.
В декларативном подходе мы можем иметь некоторые начальные ингредиенты или начальное состояние, например textInput=“”
, единственная переменная.
Возможно, ввод текста начинается с пустой строки.
Мы берем это начальное состояние и применяем его к набору правил, определенных в вашем приложении.
Если пользователь вводит текст, обновите ввод текста. Ну, сейчас это не относится.
Если шаблон отображается, рассчитайте виджет.
- Если textInput обновлен, перерисовать шаблон.
Ну, ничего из этого не применимо, поэтому программа просто будет ждать события.
Поэтому в какой-то момент пользователь обновляет ввод текста, и тогда мы можем применить правило № 1.
Мы можем обновить это до “abcd”
Таким образом, мы только что обновили наши обновления text и textInput, правило № 2 не применяется, правило № 3 говорит, что если ввод текста является обновлением, которое только что произошло, затем повторно отображаем шаблон, а затем мы возвращаемся к правилу 2, которое говорит, что шаблон отображен, рассчитать виджет, ладно, давайте посчитаем виджет.
В целом, как программисты, мы хотим стремиться к более декларативным проектам программирования.
Императив кажется более ясным и очевидным, но декларативный подход очень хорошо масштабируется для больших приложений.
Я думаю, что функциональное программирование можно выразить императивно:
- Использование множества проверок состояния объектов и if... else
/ switch
заявления
- Некоторый тайм-аут / механизм ожидания, чтобы позаботиться об асинхронности
С таким подходом возникают огромные проблемы:
- Правила / процедуры повторяются - Statefulness оставляет шансы на побочные эффекты / ошибки
Функциональное программирование, рассматривающее функции / методы как объекты и охватывающее безгражданство, рождается для решения этих проблем.
Пример использования: приложения внешнего интерфейса, такие как логика Android, iOS или веб-приложений, вкл. связь с бэкэндом
Кажется, существует много мнений о том, что такое функциональные программы и что такое императивные программы.
Я думаю, что функциональные программы проще всего описать как ориентированные на «ленивые вычисления». Вместо счетчика программ, повторяющего инструкции, язык по своей конструкции использует рекурсивный подход.
В функциональном языке вычисление функции должно начинаться с оператора return и возвращаться назад, пока в конечном итоге не будет достигнуто значение. Это имеет далеко идущие последствия в отношении синтаксиса языка.
Императив: транспортировка компьютера
Ниже я попытался проиллюстрировать это, используя аналогию с почтовым отделением. Императивный язык будет отправлять компьютер по разным алгоритмам, а затем возвращать компьютер с результатом.
Функционал: доставка рецептов
Функциональный язык будет рассылать рецепты, и когда вам нужен результат, компьютер начнет обрабатывать рецепты.
Таким образом, вы гарантируете, что не тратите слишком много циклов ЦП на работу, которая никогда не используется для вычисления результата.
Когда вы вызываете функцию на функциональном языке, возвращаемое значение представляет собой рецепт, состоящий из рецептов, который, в свою очередь, состоит из рецептов. Эти рецепты на самом деле известны как замыкания .
// helper function, to illustrate the point
function unwrap(val) {
while (typeof val === "function") val = val();
return val;
}
function inc(val) {
return function() { unwrap(val) + 1 };
}
function dec(val) {
return function() { unwrap(val) - 1 };
}
function add(val1, val2) {
return function() { unwrap(val1) + unwrap(val2) }
}
// lets "calculate" something
let thirteen = inc(inc(inc(10)))
let twentyFive = dec(add(thirteen, thirteen))
// MAGIC! The computer still has not calculated anything.
// 'thirteen' is simply a recipe that will provide us with the value 13
// lets compose a new function
let doubler = function(val) {
return add(val, val);
}
// more modern syntax, but it's the same:
let alternativeDoubler = (val) => add(val, val)
// another function
let doublerMinusOne = (val) => dec(add(val, val));
// Will this be calculating anything?
let twentyFive = doubler(thirteen)
// no, nothing has been calculated. If we need the value, we have to unwrap it:
console.log(unwrap(thirteen)); // 26
Функция unwrap оценит все функции до скалярного значения.
Последствия дизайна языка
Некоторые приятные функции в императивных языках невозможны в функциональных языках. Например
С другой стороны, императивные языки могут заимствовать отличные идеи из функциональных языков и превращаться в гибриды.
Функциональные языки испытывают большие трудности с унарными операторами , например
Реализация унарного оператора должна быть реализована примерно так:
let value = 10;
function increment_operator(value) {
return function() {
unwrap(value) + 1;
}
}
value++ // would "under the hood" become value = increment_operator(value)
Обратите внимание, что
Теперь очевидно, что применение приращения тысячу раз заставит нас обернуть значение 10000 замыканий, что бесполезно.
Более очевидный подход состоит в том, чтобы фактически напрямую изменить значение на месте , но вуаля: вы ввели изменяемые значения, также известные как изменяемые значения , которые делают язык императивным или фактически гибридным.
Под капотом это сводится к двум различным подходам к получению выходных данных при наличии входных данных.
Ниже я попытаюсь сделать иллюстрацию города со следующими элементами:
- Компьютер
- Твой дом
- Фибоначчи
Императивные языки
Задача: вычислить 3-е число Фибоначчи. Шаги:
Поместите компьютер в коробку и пометьте его липкой запиской :
Вот и все . Эта заметка теперь представляет собой результат вычисления
Мы прикрепили параметр 3 к рецепту с именем
Пример функционального Javascript
Я работал над разработкой языка программирования под названием Charm, и вот как числа Фибоначчи будут выглядеть на этом языке.
fib: (n) => if (
n < 2 // test
n // when true
fib(n-1) + fib(n-2) // when false
)
print(fib(4));
Этот код может быть скомпилирован как в императивный, так и в функциональный «байт-код».
Императивная версия javascript будет:
let fib = (n) =>
n < 2 ?
n :
fib(n-1) + fib(n-2);
HALF функциональная версия javascript будет:
let fib = (n) => () =>
n < 2 ?
n :
fib(n-1) + fib(n-2);
Функциональная версия javascript PURE будет гораздо более сложной, потому что javascript не имеет функциональных эквивалентов.
let unwrap = ($) =>
typeof $ !== "function" ? $ : unwrap($());
let $if = ($test, $whenTrue, $whenFalse) => () =>
unwrap($test) ? $whenTrue : $whenFalse;
let $lessThen = (a, b) => () =>
unwrap(a) < unwrap(b);
let $add = ($value, $amount) => () =>
unwrap($value) + unwrap($amount);
let $sub = ($value, $amount) => () =>
unwrap($value) - unwrap($amount);
let $fib = ($n) => () =>
$if(
$lessThen($n, 2),
$n,
$add( $fib( $sub($n, 1) ), $fib( $sub($n, 2) ) )
);
Я вручную «скомпилирую» его в код javascript:
"use strict";
// Library of functions:
/**
* Function that resolves the output of a function.
*/
let $$ = (val) => {
while (typeof val === "function") {
val = val();
}
return val;
}
/**
* Functional if
*
* The $ suffix is a convention I use to show that it is "functional"
* style, and I need to use $$() to "unwrap" the value when I need it.
*/
let if$ = (test, whenTrue, otherwise) => () =>
$$(test) ? whenTrue : otherwise;
/**
* Functional lt (less then)
*/
let lt$ = (leftSide, rightSide) => () =>
$$(leftSide) < $$(rightSide)
/**
* Functional add (+)
*/
let add$ = (leftSide, rightSide) => () =>
$$(leftSide) + $$(rightSide)
// My hand compiled Charm script:
/**
* Functional fib compiled
*/
let fib$ = (n) => if$( // fib: (n) => if(
lt$(n, 2), // n < 2
() => n, // n
() => add$(fib$(n-2), fib$(n-1)) // fib(n-1) + fib(n-2)
) // )
// This takes a microsecond or so, because nothing is calculated
console.log(fib$(30));
// When you need the value, just unwrap it with $$( fib$(30) )
console.log( $$( fib$(5) ))
// The only problem that makes this not truly functional, is that
console.log(fib$(5) === fib$(5)) // is false, while it should be true
// but that should be solveable
Я знаю, что этот вопрос старше, и другие уже хорошо его объяснили. Я хотел бы привести пример проблемы, которая объясняет то же самое в простых терминах.
Проблема: написание таблицы 1.
Решение: -
По повелительному стилю: =>
1*1=1
1*2=2
1*3=3
.
.
.
1*n=n
По функциональному стилю: =>
1
2
3
.
.
.
n
Пояснения в императивном стиле мы пишем инструкции более явно и которые можно назвать более упрощенными.
Где, как в функциональном стиле, вещи, которые говорят сами за себя, будут игнорироваться.