В чем разница между процедурным программированием и функциональным программированием?
Я прочитал статьи в Википедии как для процедурного программирования, так и function программирования, но я все еще немного смущен. Может ли кто-нибудь свернуть его до ядра?
Ответы
Ответ 1
Функциональный язык (в идеале) позволяет вам написать математическую функцию, то есть функцию, которая принимает n аргументов и возвращает значение. Если программа выполняется, эта функция логически оценивается при необходимости. 1
С другой стороны, процедурный язык выполняет ряд последовательных шагов. (Существует способ преобразования последовательной логики в функциональную логику, называемый стилем передачи продолжения.)
Как следствие, чисто функциональная программа всегда дает одно и то же значение для ввода, и порядок оценки не является четко определенным; Это означает, что неопределенные значения, такие как пользовательский ввод или случайные значения, трудно моделировать на чисто функциональных языках.
1 Как и все в этом ответе, это обобщение. Свойство вычисления вычисления, когда его результат необходим, а не последовательно, где он вызывается, известно как "лень". Не все функциональные языки на самом деле являются универсально ленивыми, и лень не ограничивается функциональным программированием. Скорее, приведенное здесь описание предоставляет "ментальную основу" для размышления о различных стилях программирования, которые не являются отдельными и противоположными категориями, а скорее текучими идеями.
Ответ 2
В принципе, два стиля, как Инь и Ян. Один организован, а другой - хаотичный. Бывают ситуации, когда функциональное программирование является очевидным выбором, а в других ситуациях предпочтительным является программирование. Вот почему есть, по крайней мере, два языка, которые недавно появились с новой версией, которая охватывает оба стиля программирования. (Perl 6 и D 2)
Процессуальное:
- Вывод процедуры не всегда имеет прямую корреляцию с входом.
- Все сделано в определенном порядке.
- Выполнение процедуры может иметь побочные эффекты.
- Как правило, подчеркивает реализацию решений линейным способом.
sub factorial ( UInt:D $n is copy ) returns UInt {
# modify "outside" state
state $call-count++;
# in this case it is rather pointless as
# it can't even be accessed from outside
my $result = 1;
loop ( ; $n > 0 ; $n-- ){
$result *= $n;
}
return $result;
}
int factorial( int n ){
int result = 1;
for( ; n > 0 ; n-- ){
result *= n;
}
return result;
}
Функциональные:
- Часто рекурсивный.
- Всегда возвращает тот же вывод для данного ввода.
- Порядок оценки обычно undefined.
- Должен быть без гражданства. то есть никакая операция не может иметь побочных эффектов.
- Хорошо подходит для параллельного выполнения
- Тенденции подчеркивают подход к разделению и завоеванию.
- Может быть, функция Lazy Evaluation.
(скопировано из Wikipedia);
fac :: Integer -> Integer
fac 0 = 1
fac n | n > 0 = n * fac (n-1)
или в одной строке:
fac n = if n > 0 then n * fac (n-1) else 1
proto sub factorial ( UInt:D $n ) returns UInt {*}
multi sub factorial ( 0 ) { 1 }
multi sub factorial ( $n ) { $n * samewith $n-1 } # { $n * factorial $n-1 }
pure int factorial( invariant int n ){
if( n <= 1 ){
return 1;
}else{
return n * factorial( n-1 );
}
}
Боковое примечание:
Factorial на самом деле является распространенным примером, показывающим, насколько легко создавать новые операторы в Perl 6 так же, как вы создавали бы подпрограмму. Эта функция настолько укоренена в Perl 6, что большинство операторов в реализации Rakudo определены таким образом. Он также позволяет добавлять собственные кандидаты к существующим операторам.
sub postfix:< ! > ( UInt:D $n --> UInt )
is tighter(&infix:<*>)
{ [*] 2 .. $n }
say 5!; # 120
В этом примере также показано создание диапазона (2..$n
) и метаоператор сокращения списка ([ OPERATOR ] LIST
) в сочетании с оператором умножения числовых инфикс. (*
)
Он также показывает, что после него вы можете поместить --> UInt
в подпись вместо returns UInt
.
(Вы можете уйти со стартом диапазона с помощью 2
, поскольку оператор умножения "возвращает" 1
при вызове без каких-либо аргументов)
Ответ 3
Я никогда не видел это определение, данное в другом месте, но я думаю, что это суммирует различия, данные здесь достаточно хорошо:
Функциональное программирование фокусируется на выражениях
Процедурное программирование фокусируется на операторах
Выражения имеют значения. Функциональной программой является выражение, значение которого представляет собой последовательность команд для выполнения компьютером.
Заявления не имеют значений и вместо этого изменяют состояние какой-либо концептуальной машины.
В чисто функциональном языке не было бы никаких утверждений в том смысле, что нет никакого способа манипулировать состоянием (у них все еще может быть синтаксическая конструкция с именем "statement", но если она не манипулирует состоянием, я бы не назвал ее утверждением в этом смысле). На чисто процедурном языке не было бы выражений, все было бы инструкцией, которая манипулирует состоянием машины.
Haskell будет примером чисто функционального языка, потому что нет никакого способа манипулировать состоянием. Машинный код был бы примером чисто процедурного языка, потому что все в программе - это оператор, который управляет состоянием регистров и памятью машины.
Запутанная часть состоит в том, что подавляющее большинство языков программирования содержат и выражения и выражения, позволяющие смешивать парадигмы. Языки могут быть классифицированы как более функциональные или более процедурные, основанные на том, насколько они поощряют использование выражений против выражений.
Например, C будет более функциональным, чем COBOL, потому что вызов функции является выражением, тогда как вызов подпрограммы в COBOL - это оператор (который управляет состоянием общих переменных и не возвращает значение). Python будет более функциональным, чем C, потому что он позволяет вам выражать условную логику как выражение, используя оценку короткого замыкания (test & path1 || path2 в отличие от операторов if). Схема будет более функциональной, чем Python, потому что все в схеме является выражением.
Вы все равно можете писать в функциональном стиле на языке, который поощряет процедурные парадигмы и наоборот. Это сложнее и/или более неудобно писать в парадигме, которая не поощряется языком.
Ответ 4
В информатике функциональное программирование представляет собой парадигму программирования, которая рассматривает вычисления как оценку математических функций и избегает состояния и изменяемых данных. Он подчеркивает применение функций, в отличие от стиля процедурного программирования, который подчеркивает изменения состояния.
Ответ 5
Я считаю, что процедурное/функциональное/объективное программирование - это то, как подойти к проблеме.
В первом стиле будет все, что нужно для шагов, и решает проблему, выполняя один шаг (процедуру) за раз. С другой стороны, функциональное программирование будет подчеркивать подход "разделяй и властвуй", где проблема делится на подзадачу, затем решается каждая подзадача (создание функции для решения этой подвыборки), и результаты объединяются в создайте ответ на всю проблему. Наконец, объективное программирование будет имитировать реальный мир, создавая мини-мир внутри компьютера с множеством объектов, каждый из которых обладает (несколько) уникальными характеристиками и взаимодействует с другими. Из этих взаимодействий результат будет появляться.
Каждый стиль программирования имеет свои преимущества и недостатки. Следовательно, делать что-то вроде "чистого программирования" (т.е. Чисто процедурного - никто не делает этого, кстати, что-то вроде странного, или чисто функционального или чисто объективного), очень сложно, если не невозможно, кроме некоторых элементарных проблем специально разработанный, чтобы продемонстрировать преимущество стиля программирования (поэтому мы называем тех, кто любит чистоту "weenie": D).
Затем из этих стилей у нас есть языки программирования, которые предназначены для оптимизации для каждого стиля. Например, Ассамблея все о процедурной. Хорошо, что большинство ранних языков являются процедурными, а не только Asm, например C, Pascal (и Fortran, я слышал). Тогда у нас есть вся известная Java в объективной школе (на самом деле, Java и С# также находятся в классе под названием "деньги-ориентированные", но это подлежит другому обсуждению). Также объектив - Smalltalk. В функциональной школе у нас были бы "почти функциональные" (некоторые считали их нечистыми) Lisp семья и семья ML и многие "чисто функциональные" Haskell, Erlang и т.д. Кстати, существует много общих языков, таких как Perl, Python, Ruby.
Ответ 6
Чтобы развернуть комментарий Konrad:
Как следствие, чисто функциональная программа всегда дает одно и то же значение для ввода, а порядок оценки не определен,
Из-за этого функциональный код, как правило, легче распараллеливать. Поскольку (как правило) нет побочных эффектов функций, и они (как правило) просто действуют на свои аргументы, многие проблемы concurrency исчезают.
Функциональное программирование также используется, когда вам нужно доказать, что ваш код верен. Это намного сложнее сделать с процедурным программированием (непросто с функциональным, но все же проще).
Отказ от ответственности: я не использовал функциональное программирование в течение многих лет, и только недавно начал снова смотреть на него, поэтому я, возможно, не совсем прав.:)
Ответ 7
Одна вещь, которую я не видел на самом деле, особо подчеркивает, что современные функциональные языки, такие как Haskell, действительно больше для функций первого класса для управления потоком, чем явная рекурсия. Вам не нужно определять факториальную рекурсию в Haskell, как это было сделано выше. Я думаю что-то вроде
fac n = foldr (*) 1 [1..n]
является совершенно идиоматической конструкцией и гораздо ближе по духу к использованию цикла, чем к использованию явной рекурсии.
Ответ 8
Функциональное программирование идентично процедурному программированию, в котором глобальные переменные не используются.
Ответ 9
Процессуальные языки имеют тенденцию отслеживать состояние (используя переменные) и имеют тенденцию выполнять как последовательность шагов. Чисто функциональные языки не отслеживают состояние, используют неизменные значения и, как правило, выполняют ряд зависимостей. Во многих случаях статус стека вызовов будет содержать информацию, которая будет эквивалентна информации, которая будет храниться в переменных состояния в процедурном коде.
Рекурсия - классический пример программирования функционального стиля.
Ответ 10
Конрад сказал:
Как следствие, чисто функциональная программа всегда дает одно и то же значение для ввода, и порядок оценки не определен; что означает, что неопределенные значения, такие как пользовательский ввод или случайные значения трудно моделировать на чисто функциональных языках.
Порядок оценки в чисто функциональной программе может быть затруднен (особенно с лени) или даже несущественным, но я думаю, что если говорить, что это не совсем правильно, это звучит так, как будто вы не можете сказать, программа будет работать вообще!
Возможно, лучшее объяснение состоит в том, что поток управления в функциональных программах основан на том, когда требуется значение аргументов функции. Хорошая вещь об этом, что в хорошо написанных программах состояние становится явным: каждая функция перечисляет свои входы в качестве параметров, а не произвольно munging global state. Поэтому на каком-то уровне легче рассуждать о порядке оценки по одной функции за раз. Каждая функция может игнорировать остальную вселенную и сосредоточиться на том, что ей нужно делать. При объединении функции гарантируют, что они будут работать одинаково [1], поскольку они будут изолированы.
... неопределенные значения, такие как пользовательский ввод или случайные значения, трудно моделировать чисто функциональные языки.
Решение проблемы ввода в чисто функциональных программах заключается в том, чтобы внедрить императивный язык в качестве DSL с помощью достаточно мощная абстракция. На императивных (или нечистофункциональных) языках это не требуется, потому что вы можете "обманывать" и пропускать состояние неявно, а порядок оценки явно (нравится вам это или нет). Из-за этого "обмана" и принудительной оценки всех параметров каждой функции в императивных языках 1) вы теряете способность создавать свои собственные механизмы управления потоком (без макросов), 2) код по своей сути не является потокобезопасным и/или параллелизуемым по умолчанию, 3) и реализация чего-то вроде undo (time travel) берет тщательную работу (императивный программист должен сохранить рецепт для возврата старого значения!), тогда как чистое функциональное программирование покупает вас всех эти вещи, и еще несколько, я, возможно, забыл "бесплатно".
Надеюсь, это не похоже на фанатизм, я просто хотел добавить некоторую перспективу. Императивное программирование и особенно смешанное программирование парадигмы на мощных языках, таких как С# 3.0, по-прежнему являются абсолютно эффективными способами, чтобы сделать что-то, и нет серебряной пули.
[1]... кроме, возможно, с учетом использования памяти (см. foldl и foldl 'в Haskell).
Ответ 11
Чтобы развернуть комментарий Konrad:
и порядок оценки не четко определенные
Некоторые функциональные языки имеют так называемую "ленивую оценку". Это означает, что функция не выполняется до тех пор, пока значение не понадобится. До этого времени сама функция - это то, что передается.
Процессуальные языки - это шаг 1, шаг 2, шаг 3... если в шаге 2 вы добавляете 2 + 2, он делает это правильно. В ленивой оценке вы бы сказали, добавьте 2 + 2, но если результат никогда не используется, он никогда не добавляет.
Ответ 12
Если у вас есть шанс, я бы рекомендовал получить копию Lisp/Scheme и выполнить некоторые проекты. Большинство идей, которые в последнее время стали бандами, были выражены в Lisp десятилетие назад: функциональное программирование, продолжение (как закрытие), сборка мусора и даже XML.
Итак, это был бы хороший способ получить начало во всех этих текущих идеях и еще несколько, например, символическое вычисление.
Вы должны знать, для чего функциональное программирование полезно, и для чего это плохо. Это не хорошо для всего. Некоторые проблемы лучше всего выражаются в терминах побочных эффектов, когда один и тот же вопрос дает разные ответы в зависимости от того, когда он задан.
Ответ 13
Функциональное программирование
num = 1
def function_to_add_one(num):
num += 1
return num
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
#Final Output: 2
Процедурное программирование
num = 1
def procedure_to_add_one():
global num
num += 1
return num
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
#Final Output: 6
function_to_add_one
- это функция
procedure_to_add_one
- это процедура
Даже если вы запустите функцию пять раз, каждый раз она будет возвращаться 2
Если вы запустите процедуру пять раз, в конце пятого прогона вы получите 6.
Ответ 14
@Creighton:
В Haskell есть библиотечная функция, называемая product:
prouduct list = foldr 1 (*) list
или просто:
product = foldr 1 (*)
поэтому "идиоматический" факториал
fac n = foldr 1 (*) [1..n]
будет просто
fac n = product [1..n]
Ответ 15
Процедурное программирование делит последовательности операторов и условных конструкций на отдельные блоки, называемые процедурами, параметризованными над аргументами, которые являются (нефункциональными) значениями.
Функциональное программирование одно и то же, за исключением того, что функции являются первоклассными значениями, поэтому они могут передаваться как аргументы другим функциям и возвращаться в виде результатов от вызовов функций.
Обратите внимание, что функциональное программирование является обобщением процедурного программирования в этой интерпретации. Тем не менее, меньшинство интерпретирует "функциональное программирование" как означающее "побочный эффект", который совершенно другой, но несущественный для всех основных функциональных языков, кроме Haskell.
Ответ 16
Чтобы понять разницу, нужно понять, что парадигма "крестного отца" как процедурного, так и функционального программирования является императивным программированием.
По сути, процедурное программирование - это просто способ структурирования императивных программ, в которых основным методом абстракции является "процедура". (или "функция" в некоторых языках программирования). Даже объектно-ориентированное программирование - это просто еще один способ структурирования императивной программы, где состояние инкапсулируется в объекты, превращаясь в объект с "текущим состоянием", плюс этот объект имеет набор функций, методов и других вещей, которые позволяют вам программист манипулирует или обновляет состояние.
Теперь, что касается функционального программирования, суть его подхода заключается в том, что он определяет, какие значения принимать и как эти значения следует передавать. (таким образом, нет никакого состояния, и нет изменяемых данных, поскольку он принимает функции в качестве значений первого класса и передает их в качестве параметров другим функциям).
PS: понимание каждой используемой парадигмы программирования должно прояснить различия между ними.
PSS: В конце концов, парадигмы программирования - это просто разные подходы к решению проблем.
PSS: у этого корального ответа есть отличное объяснение.
Ответ 17
Ни один из ответов здесь не показывает идиоматическое функциональное программирование. Рекурсивный факторный ответ отлично подходит для представления рекурсии в FP, но большая часть кода не является рекурсивной, поэтому я не думаю, что ответ является полностью репрезентативным.
Допустим, у вас есть массивы строк, и каждая строка представляет целое число, например "5" или "-200". Вы хотите проверить этот входной массив строк по вашему внутреннему тестовому примеру (используя целочисленное сравнение). Оба решения показаны ниже
процедурный
arr_equal(a : [Int], b : [Str]) -> Bool {
if(a.len != b.len) {
return false;
}
bool ret = true;
for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
int a_int = a[i];
int b_int = parseInt(b[i]);
ret &= a_int == b_int;
}
return ret;
}
функциональная
eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization
arr_equal(a : [Int], b : [Str]) -> Bool =
zip(a, b.map(toInt)) # Combines into [Int, Int]
.map(eq)
.reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value
В то время как чисто функциональные языки обычно являются исследовательскими языками (поскольку реальный мир любит бесплатные побочные эффекты), в реальных процедурных языках будет использоваться гораздо более простой функциональный синтаксис, когда это уместно.
Обычно это реализуется с помощью внешней библиотеки, такой как Lodash, или доступной встроенной в более новые языки, такие как Rust. Тяжелая работа по функциональному программированию выполняется с помощью таких функций/концепций, как map
, filter
, reduce
, currying
, partial
, последние три из которых вы можете найти для дальнейшего понимания.
добавление
Для использования в дикой природе компилятору обычно приходится придумывать, как внутренне преобразовать функциональную версию в процедурную, так как накладные расходы на вызов функции слишком велики. Рекурсивные случаи, такие как показанный факториал, будут использовать трюки, такие как хвостовой вызов, для удаления использования памяти O (n). Отсутствие побочных эффектов позволяет функциональным компиляторам реализовывать оптимизацию && ret
даже когда .reduce
выполняется последним. Использование Lodash в JS, очевидно, не допускает какой-либо оптимизации, поэтому это удар по производительности (что обычно не касается веб-разработки). Такие языки, как Rust, будут внутренне оптимизированы (и будут иметь такие функции, как try_fold
чтобы помочь && ret
оптимизации).