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


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

14 198

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

функционал:

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

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 }

D 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 при вызове без аргументов )

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

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

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

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

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

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

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

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

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

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

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

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

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

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

затем, из этих стилей, у нас есть языки программирования, которые предназначены для оптимизации для некоторых каждого стиля. Например, сборка-это все о процедурном. Хорошо, большинство ранних языков являются процедурными, а не только Asm, такими как C, Pascal, (и Фортран, я слышал). Затем у нас есть все известные Java в объективной школе (на самом деле 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.