В чем разница между процедурным программированием и функциональным программированием?
Я читал статьи в Википедии как для процедурного программирования, так и для функционального программирования , но я все еще немного смущен. Может ли кто-нибудь свернуть его до ядра?
- Является ли оператор monad bind (>> =) ближе к функциональному составу (цепочке) или приложению функции?
- Существует ли игла Haskell для обновления вложенной структуры данных?
- Заказ параметров для использования каррирования
- Что такое zip (функциональное программирование?)
- Как использовать (->) экземпляры Monad и путаницу о (->)
- Злоупотребление алгеброй алгебраических типов данных - почему это работает?
- Idiomatic R-код для разбиения вектора по индексу и выполнения операции над этим разделом
- Как заменить циклы с альтернативным функциональным программированием без оптимизации хвостового вызова?
Функциональный язык (в идеале) позволяет написать математическую функцию, то есть функцию, которая принимает 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 Evaluation.
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 ); } }
Примечание:
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
при вызове без каких-либо аргументов)
Я никогда не видел это определение, данное в другом месте, но я думаю, что это суммирует различия, данные здесь достаточно хорошо:
Функциональное программирование фокусируется на выражениях
Процедурное программирование фокусируется на заявлениях
Выражения имеют значения. Функциональная программа – это выражение, значение которого представляет собой последовательность инструкций для компьютера для выполнения.
Заявления не имеют значений и вместо этого изменяют состояние какой-либо концептуальной машины.
В чисто функциональном языке не было бы никаких утверждений в том смысле, что нет никакого способа манипулировать состоянием (у них может быть синтаксическая конструкция с именем «statement», но если она не манипулирует состоянием, я бы не назвал ее утверждением в этом смысле ). В чисто процедурном языке не было бы выражений, все было бы инструкцией, которая манипулирует состоянием машины.
Хаскелл был бы примером чисто функционального языка, потому что невозможно манипулировать государством. Машинный код будет примером чисто процедурного языка, потому что все в программе – это оператор, который управляет состоянием регистров и памятью машины.
Запутанная часть состоит в том, что подавляющее большинство языков программирования содержат как выражения, так и выражения, позволяющие смешивать парадигмы. Языки могут быть classифицированы как более функциональные или более процедурные, исходя из того, насколько они поощряют использование выражений и выражений.
Например, C будет более функциональным, чем COBOL, потому что вызов функции является выражением, тогда как вызов подпрограммы в COBOL – это оператор (который управляет состоянием общих переменных и не возвращает значение). Python будет более функциональным, чем C, потому что он позволяет вам выражать условную логику как выражение, используя оценку короткого замыкания (test && path1 || path2 в отличие от операторов if). Схема будет более функциональной, чем Python, потому что все в схеме является выражением.
Вы все еще можете писать в функциональном стиле на языке, который поощряет процедурные парадигмы и наоборот. Сложнее и / или более неудобно писать в парадигме, которая не поощряется языком.
В информатике функциональное программирование представляет собой парадигму программирования, которая рассматривает вычисления как оценку математических функций и избегает состояния и изменяемых данных. В нем подчеркивается применение функций, в отличие от стиля процедурного программирования, который подчеркивает изменения в состоянии.
Я считаю, что процедурное / функциональное / объективное программирование – это подход к проблеме.
Первый стиль планировал бы все в шагах и решает проблему, выполняя один шаг (процедуру) за раз. С другой стороны, функциональное программирование будет подчеркивать подход «разделяй и властвуй», где проблема делится на подзадачу, затем решается каждая подзадача (создание функции для решения этой подвыборки), и результаты объединяются в создайте ответ на всю проблему. Наконец, объективное программирование будет имитировать реальный мир, создавая мини-мир внутри компьютера с множеством объектов, каждый из которых обладает (несколько) уникальными характеристиками и взаимодействует с другими. Из этих взаимодействий результат будет возникать.
Каждый стиль программирования имеет свои преимущества и недостатки. Следовательно, делать что-то вроде «чистого программирования» (т. Е. Чисто процедурного – никто не делает этого, кстати, что-то вроде странного, или чисто функционального или чисто объективного), очень сложно, если не невозможно, кроме некоторых элементарных проблем специально разработанный, чтобы продемонстрировать преимущество стиля программирования (следовательно, мы называем тех, кто любит чистоту «weenie»: D).
Затем из этих стилей у нас есть языки программирования, которые предназначены для оптимизации для каждого стиля. Например, Ассамблея все о процедурной. Хорошо, что большинство ранних языков являются процедурными, а не только Asm, например C, Pascal (и Fortran, я слышал). Тогда у нас есть вся известная Java в объективной школе (на самом деле, Java и C # также находятся в classе под названием «деньги-ориентированные», но это подлежит другому обсуждению). Также объектив – Smalltalk. В функциональной школе у нас были бы «почти функциональные» (некоторые считали их нечистыми). Семья Lisp и семейство ML и многие «чисто функциональные» Haskell, Erlang и т. Д. Кстати, существует много общих языков, таких как Perl, Python , Рубин.
Чтобы расширить комментарий Konrad:
Как следствие, чисто функциональная программа всегда дает одинаковое значение для ввода, а порядок оценки не определен;
Из-за этого функциональный код, как правило, легче распараллеливать. Поскольку (как правило) нет побочных эффектов функций, и они (как правило) просто действуют на свои аргументы, многие проблемы параллелизма уходят.
Функциональное программирование также используется, когда вам нужно доказать, что ваш код верен. Это гораздо сложнее сделать с процедурным программированием (непросто с функциональным, но все же проще).
Отказ от ответственности: я не использовал функциональное программирование годами, и только недавно начал смотреть на него снова, поэтому я, возможно, не совсем прав. 🙂
Одна вещь, которую я не видел на самом деле, особо подчеркивает, что современные функциональные языки, такие как Haskell, действительно больше относятся к функциям первого classа для управления streamом, чем к явной рекурсии. Вам не нужно определять факториальную рекурсию в Haskell, как это было сделано выше. Я думаю, что-то вроде
fac n = foldr (*) 1 [1..n]
является совершенно идиоматической конструкцией и гораздо ближе по духу к использованию цикла, чем к использованию явной рекурсии.
Процессуальные языки имеют тенденцию отслеживать состояние (используя переменные) и, как правило, выполняют последовательность шагов. Чисто функциональные языки не отслеживают состояние, используют неизменные значения и, как правило, выполняют ряд зависимостей. Во многих случаях статус стека вызовов будет содержать информацию, которая будет эквивалентна информации, которая будет храниться в переменных состояния в процедурном коде.
Рекурсия – classический пример программирования функционального стиля.
Конрад сказал:
Как следствие, чисто функциональная программа всегда дает одинаковое значение для ввода, а порядок оценки не определен; что неопределенные значения, такие как пользовательский ввод или случайные значения, трудно моделировать на чисто функциональных языках.
Порядок оценки в чисто функциональной программе может быть затруднен (особенно с лени) или даже несущественным, но я думаю, что если говорить, что это не совсем правильно, это звучит так, как будто вы не можете сказать, идет ли ваша программа работать на всех!
Возможно, лучшее объяснение заключается в том, что stream управления в функциональных программах основан на том, когда необходимо значение аргументов функции. Хорошая вещь об этом, что в хорошо написанных программах состояние становится явным: каждая функция перечисляет свои входы в качестве параметров, а не произвольно перемещает глобальное состояние. Поэтому на каком-то уровне легче рассуждать о порядке оценки по одной функции за раз . Каждая функция может игнорировать остальную вseleniumную и сосредоточиться на том, что ей нужно делать. При объединении функции гарантируют, что они будут работать одинаково [1], как в отдельности.
… неопределенные значения, такие как ввод пользователя или случайные значения, трудно моделировать на чисто функциональных языках.
Решение проблемы ввода в чисто функциональных программах заключается в том, чтобы внедрить императивный язык в качестве DSL, используя достаточно мощную абстракцию . На императивных (или нечистофункциональных) языках это не требуется, потому что вы можете «обманывать» и пропускать состояние неявно, а порядок оценки явно (нравится вам это или нет). Из-за этого «обмана» и принудительной оценки всех параметров каждой функции в императивных языках 1) вы теряете способность создавать свои собственные механизмы управления streamом (без макросов), 2) код по своей сути не является streamобезопасным и / или параллелизуемым по умолчанию , 3) и реализация чего-то вроде отмены (время проезда) берет тщательную работу (императивный программист должен сохранить рецепт для возврата старого значения!), тогда как чистое функциональное программирование покупает вам все эти вещи – и еще несколько Возможно, я забыл – «бесплатно».
Надеюсь, это не похоже на фанатизм, я просто хотел добавить некоторую перспективу. Императивное программирование и особенно смешанное программирование парадигмы на мощных языках, таких как C # 3.0, по-прежнему являются полностью эффективными способами добиться успеха, и нет серебряной пули .
[1] … кроме, возможно, с учетом использования памяти (см. Foldl и foldl ‘в Haskell).
Чтобы расширить комментарий Konrad:
и порядок оценки не определен
Некоторые функциональные языки имеют так называемую «ленивую оценку». Это означает, что функция не выполняется до тех пор, пока значение не понадобится. До этого времени сама функция – это то, что передается.
Процедурные языки – это шаг 1, шаг 2, шаг 3 … если в шаге 2 вы добавляете 2 + 2, он делает это правильно. В ленивой оценке вы бы сказали добавить 2 + 2, но если результат никогда не используется, он никогда не добавляет.
Функциональное программирование идентично процедурному программированию, в котором глобальные переменные не используются.
Если у вас есть шанс, я бы рекомендовал получить копию Lisp / Scheme и выполнить некоторые проекты в ней. Большинство идей, которые в последнее время стали bandwagons, были выражены в Lisp несколько десятилетий назад: функциональное программирование, продолжение (как закрытие), assembly мусора и даже XML.
Таким образом, это был бы хороший способ начать работу над всеми этими текущими идеями и еще несколько, например, символическое вычисление.
Вы должны знать, для чего полезно функциональное программирование, и для чего это плохо. Это не хорошо для всего. Некоторые проблемы лучше всего выражаются в терминах побочных эффектов, когда один и тот же вопрос дает разные ответы в зависимости от того, когда его спрашивают.
@Creighton:
В Haskell есть библиотечная функция, называемая продуктом :
prouduct list = foldr 1 (*) list
или просто:
product = foldr 1 (*)
поэтому «идиоматический» факториал
fac n = foldr 1 (*) [1..n]
будет просто
fac n = product [1..n]
Процедурное программирование делит последовательности операторов и условных конструкций на отдельные блоки, называемые процедурами, которые параметризуются над аргументами, которые являются (нефункциональными) значениями.
Функциональное программирование одно и то же, за исключением того, что функции являются первоclassными значениями, поэтому они могут передаваться как аргументы другим функциям и возвращаться в виде результатов от вызовов функций.
Обратите внимание, что функциональное программирование является обобщением процедурного программирования в этой интерпретации. Тем не менее, меньшинство интерпретирует «функциональное программирование» как означающее «побочный эффект», который совершенно другой, но несущественный для всех основных функциональных языков, кроме Haskell.