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

247

Я читал статьи в Википедии как о процедурном программировании, так и о функциональном программировании , но я все еще немного сбит с толку. Может ли кто-нибудь свести это до глубины души?

Томас Оуэнс
источник
Википедия подразумевает, что FP является подмножеством (то есть всегда) декларативного программирования, но это не так и объединяет таксономию IP против DP .
Шелби Мур III

Ответы:

151

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

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

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


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

Конрад Рудольф
источник
9
Неопределенные значения, такие как пользовательский ввод или случайные значения, трудно моделировать на чисто функциональных языках, но это решаемая проблема. Смотрите монады.
Apocalisp
« последовательные шаги , где функциональная программа будет вложена» означает обеспечение разделения проблем путем выделения структуры функций , то есть разделения зависимостей между подкомпьютерами детерминированного вычисления.
Шелби Мур III
это кажется неправильным - процедуры могут быть также вложенными, процедуры могут иметь параметры
Hurda
1
@ Хурда Да, можно было бы сформулировать это лучше. Дело в том, что процедурное программирование происходит пошагово в заранее определенном порядке, тогда как функциональные программы не выполняются пошагово; скорее, значения вычисляются, когда они необходимы. Однако отсутствие общепринятого определения терминологии программирования делает такие обобщения почти бесполезными. Я исправил свой ответ на этот счет.
Конрад Рудольф
97

В основном два стиля, как Инь и Ян. Один организован, а другой хаотичен. Бывают ситуации, когда функциональное программирование является очевидным выбором, а в других ситуациях процедурное программирование является лучшим выбором. Вот почему есть как минимум два языка, которые недавно выпустили новую версию, которая охватывает оба стиля программирования. ( Perl 6 и D 2 )

Процедурный:

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

Perl 6

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;
}

Д 2

int factorial( int n ){

  int result = 1;

  for( ; n > 0 ; n-- ){
    result *= n;
  }

  return result;
}

Функциональность:

  • Часто рекурсивный.
  • Всегда возвращает один и тот же вывод для данного ввода.
  • Порядок оценки обычно не определен.
  • Должно быть без гражданства. т.е. никакая операция не может иметь побочных эффектов.
  • Хорошо подходит для параллельного исполнения
  • Стремится подчеркнуть подход «разделяй и властвуй».
  • Может иметь функцию отложенной оценки.

Haskell

(скопировано из Википедии );

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

Perl 6

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 }

Д 2

pure int factorial( invariant int n ){
  if( n <= 1 ){
    return 1;
  }else{
    return n * factorial( n-1 );
  }
}

Примечание:

Факториал на самом деле является распространенным примером, показывающим, как легко создавать новые операторы в 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при вызове без каких-либо аргументов)

Брэд Гилберт
источник
Здравствуйте, не могли бы вы предоставить пример для следующих 2 пунктов, упомянутых для «Процедурного», рассматривающего пример факторной реализации в Perl 6. 1) Вывод подпрограммы не всегда имеет прямую корреляцию с вводом. 2) Выполнение процедуры может иметь побочные эффекты.
Нага Киран
sub postfix:<!> ($n) { [*] 1..$n }
Брэд Гилберт,
@BradGilbert - Можете No operation can have side effectsли вы уточнить это?
kushalvm
2
Наверное, лучший ответ, который я когда-либо мог найти ... И я провел некоторые исследования по этим отдельным моментам ... которые действительно помогли мне! :)
Наванеет
1
@AkashBisariya sub foo( $a, $b ){ ($a,$b).pick }← не всегда возвращает один и тот же вывод для одного и того же ввода, в то время как следующее возвращаетsub foo( $a, $b ){ $a + $b }
Брэд Гилберт
70

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

Функциональное программирование фокусируется на выражениях

Процедурное программирование фокусируется на утверждениях

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

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

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

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

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

Например, C был бы более функциональным, чем COBOL, потому что вызов функции - это выражение, тогда как вызов подпрограммы в COBOL - это оператор (который манипулирует состоянием общих переменных и не возвращает значение). Python будет более функциональным, чем C, потому что он позволяет вам выражать условную логику как выражение, используя оценку короткого замыкания (test && path1 || path2 в отличие от операторов if). Схема будет более функциональной, чем Python, потому что все в схеме является выражением.

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

Omnimike
источник
2
Лучшее и самое краткое объяснение, которое я видел в Интернете, браво!
tommed
47

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

хуан
источник
4
Хотя это объяснение и помогло мне больше всего, я все еще размышляю над концепцией функционального программирования. Я ищу стиль программирования, который не зависит от ссылки на внешние объекты для запуска (каждая вещь, которую должна запустить функция, должна передаваться как параметр). Например, я никогда не вставлял GetUserContext()бы функцию, пользовательский контекст был бы передан. Это функциональное программирование? Заранее спасибо.
Мэтт Кашатт
27

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

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 .

Хамза Зубайр
источник
5
этот пример действительно прост для понимания термина «не имеющие состояния» и «неизменяемые данные» в функциональном программировании, чтение всех приведенных выше определений и различий не очистило мое замешательство до прочтения этого ответа. Спасибо!
Максимус
26

Я считаю, что процедурное / функциональное / объективное программирование - это то, как подходить к проблеме.

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

Каждый стиль программирования имеет свои преимущества и недостатки. Следовательно, делать что-то вроде «чистого программирования» (то есть чисто процедурного - кстати, никто этого не делает, что довольно странно - или чисто функционально, или чисто объективно) очень сложно, если не невозможно, за исключением некоторых элементарных проблем, особенно предназначен для демонстрации преимущества стиля программирования (поэтому мы называем тех, кто любит чистоту, "weenie": D).

Затем из этих стилей у нас есть языки программирования, которые оптимизированы для некоторых стилей. Например, Ассамблея все о процедурных. Хорошо, большинство ранних языков являются процедурными, а не только Asm, как C, Pascal (и Fortran, я слышал). Затем у нас есть все известные Java в целевой школе (на самом деле, Java и C # также находятся в классе, называемом «ориентированным на деньги», но это предмет для другого обсуждения). Также целью является Smalltalk. В функциональной школе у ​​нас были бы «почти функциональные» (некоторые считали их нечистыми) семейства Lisp и ML, а также многие «чисто функциональные» Haskell, Erlang и т. Д. Кстати, есть много общих языков, таких как Perl, Python , Рубин.

Аарон Холл
источник
13

Чтобы расширить комментарий Конрада:

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

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

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

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

Herms
источник
12

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

fac n = foldr (*) 1 [1..n]

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

С Хогг
источник
10

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

Нир О.
источник
7

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

Рекурсия - классический пример программирования функционального стиля.

Клин
источник
1
Прочитав эту страницу, я подумал о том же самом -> «Рекурсия - классический пример программирования в функциональном стиле», и вы его очистили. Спасибо, теперь я думаю, что кое-что получаю.
Мудассир Хуссейн
6

Конрад сказал:

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

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

Возможно, лучшим объяснением было бы то, что поток управления в функциональных программах основан на том, когда необходимо значение аргументов функции. Хорошая новость в том, что в хорошо написанных программах состояние становится явным: каждая функция перечисляет свои входные данные в качестве параметров, а не произвольно манипулирует глобальным состоянием. Таким образом, на некотором уровне легче рассуждать о порядке оценки по одной функции за раз . Каждая функция может игнорировать остальную часть вселенной и сосредоточиться на том, что ей нужно делать. При объединении функции гарантированно работают так же [1], как и в изоляции.

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

Решение проблемы ввода в чисто функциональных программах состоит в том, чтобы встроить императивный язык как DSL с использованием достаточно мощной абстракции . В императивных (или не чисто функциональных) языках это не нужно, потому что вы можете «обманывать» и неявно передавать состояние, а порядок вычисления является явным (нравится вам это или нет). Из-за этого «обмана» и принудительной оценки всех параметров для каждой функции в императивных языках 1) вы теряете возможность создавать свои собственные механизмы управления потоками (без макросов), 2) код не является поточно-ориентированным и / или распараллеливаемым по умолчанию, 3) и реализация чего-то вроде отмены (путешествия во времени) требует тщательной работы (императивный программист должен хранить рецепт для возврата старых значений!), Тогда как чистое функциональное программирование покупает вам все эти вещи - и еще несколько, я могу забыли - «бесплатно».

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

[1] ... за исключением, возможно, относительно использования памяти (см. Foldl и foldl 'в Haskell).

Джаред Апдайк
источник
5

Чтобы расширить комментарий Конрада:

и порядок оценки не является четко определенным

Некоторые функциональные языки имеют то, что называется Lazy Evaluation. Это означает, что функция не выполняется, пока значение не требуется. До этого момента сама функция - это то, что передается.

Процедурные языки - это шаг 1, шаг 2, шаг 3 ... если на шаге 2 вы скажете добавить 2 + 2, тогда это будет сделано правильно. В ленивой оценке вы сказали бы добавить 2 + 2, но если результат никогда не используется, он никогда не делает сложения.

Брайан Лихи
источник
4

Если у вас есть шанс, я бы порекомендовал получить копию Lisp / Scheme и выполнить в ней несколько проектов. Большинство идей, которые в последнее время стали популярными, были выражены в Лиспе десятилетия назад: функциональное программирование, продолжения (как замыкания), сборка мусора, даже XML.

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

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

Майк Данлавей
источник
3

@Creighton:

В Haskell есть библиотечная функция под названием product :

prouduct list = foldr 1 (*) list

или просто:

product = foldr 1 (*)

так что "идиоматический" факториал

fac n = foldr 1 (*)  [1..n]

будет просто

fac n = product [1..n]
Джаред Апдайк
источник
Это не дает ответа на вопрос. Чтобы критиковать или запросить разъяснения у автора, оставьте комментарий под своим постом.
Ник Китто
Я полагаю, что это было опубликовано много лет назад, до того, как была добавлена ​​система комментариев, если вы можете в это поверить: stackoverflow.com/help/badges/30/beta?userid=2543
Джаред Апдайк,
2

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

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

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

Джон Харроп
источник
1

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

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

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

PS: понимание каждой используемой парадигмы программирования должно прояснить различия между ними.

PSS: В конце концов, парадигмы программирования - это просто разные подходы к решению проблем.

PSS: у этого корального ответа есть отличное объяснение.

Фуад Букредин
источник
0

Ни один из ответов здесь не показывает идиоматическое функциональное программирование. Рекурсивный факторный ответ отлично подходит для представления рекурсии в 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оптимизации).

Николас Пипитоне
источник