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

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

14 ответов


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

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

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


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


в основном два стиля, как инь и Ян. Один организован, а другой хаотичен. Есть ситуации, когда функциональное программирование является очевидным выбором, а другие ситуации были процедурное программирование является лучшим выбором. Вот почему есть по крайней мере два языка, которые недавно вышли с новой версией, которая охватывает оба стиля программирования. ( 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;
}

D 2

int factorial( int n ){

  int result = 1;

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

  return result;
}

функционал:

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

Хаскелл

( скопированный с Википедия );

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 }

D 2

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

Примечание:

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

sub postfix:< ! > ( UInt:D $n --> UInt )
  is tighter(&infix:<*>)
  { [*] 2 .. $n }

say 5!; # 120␤

в этом примере также показано создание диапазона (2..$n) и метаоператор сокращения списка ([ OPERATOR ] LIST) в сочетании с числовым оператором умножения infix. (*)
Это также показывает, что вы можете поставить --> UInt в подписи вместо returns UInt после он.

(вы можете уйти с началом диапазона с 2 как умножить "оператор" возвращает 1 при вызове без аргументов )


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

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

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

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

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

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

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

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

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

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


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


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

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

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

затем из этих стилей у нас есть языки программирования, которые оптимизированы для некоторых стилей. Например, сборка-это все процедурные вопросы. Хорошо, большинство ранних языков процедурные, а не только Asm, как C, Pascal, (и Фортран, я слышал). Затем у нас есть знаменитая Java в objective school (на самом деле Java и C# также находятся в классе под названием "ориентированный на деньги", но это предмет для другого обсуждения). Также объективен Smalltalk. В функциональной школе мы имели бы" почти функциональную "(некоторые считали их нечистыми) семью Lisp и семью ML и много" чисто функциональных " Haskell, Erlang и т. д. Кстати, есть много общих языков, таких как Perl, Python, Ruby.


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

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

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

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

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


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

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

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


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

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


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

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

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

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

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

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

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

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


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

и порядок оценки не четко очерченный

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

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


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


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

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

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


@Creighton:

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

prouduct list = foldr 1 (*) list

или просто:

product = foldr 1 (*)

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

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

просто

fac n = product [1..n]

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

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

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