Я читал статьи в Википедии как о процедурном программировании, так и о функциональном программировании , но я все еще немного сбит с толку. Может ли кто-нибудь свести это до глубины души?
Я читал статьи в Википедии как о процедурном программировании, так и о функциональном программировании , но я все еще немного сбит с толку. Может ли кто-нибудь свести это до глубины души?
Ответы:
Функциональный язык (в идеале) позволяет вам написать математическую функцию, то есть функцию, которая принимает n аргументов и возвращает значение. Если программа выполняется, эта функция логически оценивается при необходимости. 1
С другой стороны, процедурный язык выполняет ряд последовательных шагов. (Существует способ преобразования последовательной логики в функциональную логику, называемый стилем передачи продолжения .)
Как следствие, чисто функциональная программа всегда дает одно и то же значение для ввода, и порядок оценки не является четко определенным; Это означает, что неопределенные значения, такие как пользовательский ввод или случайные значения, трудно моделировать на чисто функциональных языках.
+1 Как и все остальное в этом ответе, это обобщение. Это свойство, вычисляющее вычисление, когда требуется его результат, а не последовательно, где оно вызывается, называется «лень». Не все функциональные языки на самом деле являются универсально ленивыми, и лень не ограничивается функциональным программированием. Скорее, приведенное здесь описание предоставляет «ментальную основу» для размышления о разных стилях программирования, которые не являются отдельными и противоположными категориями, а скорее текучими идеями.
В основном два стиля, как Инь и Ян. Один организован, а другой хаотичен. Бывают ситуации, когда функциональное программирование является очевидным выбором, а в других ситуациях процедурное программирование является лучшим выбором. Вот почему есть как минимум два языка, которые недавно выпустили новую версию, которая охватывает оба стиля программирования. ( 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;
}
(скопировано из Википедии );
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 );
}
}
Факториал на самом деле является распространенным примером, показывающим, как легко создавать новые операторы в 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
при вызове без каких-либо аргументов)
sub postfix:<!> ($n) { [*] 1..$n }
No operation can have side effects
ли вы уточнить это?
sub foo( $a, $b ){ ($a,$b).pick }
← не всегда возвращает один и тот же вывод для одного и того же ввода, в то время как следующее возвращаетsub foo( $a, $b ){ $a + $b }
Я никогда не видел это определение, данное в другом месте, но я думаю, что это довольно хорошо суммирует различия, приведенные здесь:
Функциональное программирование фокусируется на выражениях
Процедурное программирование фокусируется на утверждениях
У выражений есть значения. Функциональная программа - это выражение, значение которого представляет собой последовательность инструкций для компьютера.
Утверждения не имеют значений и вместо этого изменяют состояние некоторой концептуальной машины.
В чисто функциональном языке не было бы операторов, в том смысле, что нет никакого способа манипулировать состоянием (у них все еще может быть синтаксическая конструкция с именем «оператор», но если бы он не манипулировал состоянием, я бы не назвал его оператором в этом смысле ). На чисто процедурном языке не было бы никаких выражений, все было бы инструкцией, которая манипулирует состоянием машины.
Хаскель был бы примером чисто функционального языка, потому что нет способа манипулировать состоянием. Машинный код был бы примером чисто процедурного языка, потому что все в программе - это оператор, который манипулирует состоянием регистров и памятью машины.
Смущает то, что подавляющее большинство языков программирования содержат как выражения, так и операторы, что позволяет смешивать парадигмы. Языки могут быть классифицированы как более функциональные или процедурные в зависимости от того, насколько они поощряют использование выражений против выражений.
Например, C был бы более функциональным, чем COBOL, потому что вызов функции - это выражение, тогда как вызов подпрограммы в COBOL - это оператор (который манипулирует состоянием общих переменных и не возвращает значение). Python будет более функциональным, чем C, потому что он позволяет вам выражать условную логику как выражение, используя оценку короткого замыкания (test && path1 || path2 в отличие от операторов if). Схема будет более функциональной, чем Python, потому что все в схеме является выражением.
Вы по-прежнему можете писать в функциональном стиле на языке, который поощряет процессуальную парадигму и наоборот. Просто сложнее и / или более неловко писать в парадигме, которая не поощряется языком.
В информатике функциональное программирование - это парадигма программирования, которая рассматривает вычисления как оценку математических функций и избегает состояния и изменчивых данных. Он подчеркивает применение функций, в отличие от процедурного стиля программирования, который подчеркивает изменения в состоянии.
GetUserContext()
бы функцию, пользовательский контекст был бы передан. Это функциональное программирование? Заранее спасибо.
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 .
Я считаю, что процедурное / функциональное / объективное программирование - это то, как подходить к проблеме.
Первый стиль будет планировать все по шагам и решать проблему путем реализации одного шага (процедуры) за раз. С другой стороны, в функциональном программировании акцент делается на подходе «разделяй и властвуй», когда проблема делится на подзадачу, затем решается каждая подзадача (создается функция для решения этой подзадачи) и результаты объединяются в создать ответ на всю проблему. Наконец, объективное программирование имитирует реальный мир, создавая внутри компьютера мини-мир со многими объектами, каждый из которых имеет (несколько) уникальные характеристики и взаимодействует с другими. Из этих взаимодействий получится результат.
Каждый стиль программирования имеет свои преимущества и недостатки. Следовательно, делать что-то вроде «чистого программирования» (то есть чисто процедурного - кстати, никто этого не делает, что довольно странно - или чисто функционально, или чисто объективно) очень сложно, если не невозможно, за исключением некоторых элементарных проблем, особенно предназначен для демонстрации преимущества стиля программирования (поэтому мы называем тех, кто любит чистоту, "weenie": D).
Затем из этих стилей у нас есть языки программирования, которые оптимизированы для некоторых стилей. Например, Ассамблея все о процедурных. Хорошо, большинство ранних языков являются процедурными, а не только Asm, как C, Pascal (и Fortran, я слышал). Затем у нас есть все известные Java в целевой школе (на самом деле, Java и C # также находятся в классе, называемом «ориентированным на деньги», но это предмет для другого обсуждения). Также целью является Smalltalk. В функциональной школе у нас были бы «почти функциональные» (некоторые считали их нечистыми) семейства Lisp и ML, а также многие «чисто функциональные» Haskell, Erlang и т. Д. Кстати, есть много общих языков, таких как Perl, Python , Рубин.
Чтобы расширить комментарий Конрада:
Как следствие, чисто функциональная программа всегда дает одно и то же значение для ввода, и порядок оценки не является четко определенным;
Из-за этого функциональный код обычно легче распараллелить. Поскольку (как правило) побочные эффекты функций отсутствуют, и они (в основном) просто действуют на свои аргументы, многие проблемы параллелизма исчезают.
Функциональное программирование также используется, когда вам нужно убедиться, что ваш код верен. Это намного сложнее сделать с процедурным программированием (не просто с функциональным, но все же проще).
Отказ от ответственности: я не использовал функциональное программирование в течение многих лет, и только недавно начал смотреть на него снова, поэтому я могу быть не совсем прав здесь. :)
Одна вещь, которую я не видел, действительно подчеркивалась здесь, это то, что современные функциональные языки, такие как Haskell, действительно больше относятся к функциям первого класса для управления потоком, чем к явной рекурсии. Вам не нужно определять факториал рекурсивно в Haskell, как это было сделано выше. Я думаю что-то вроде
fac n = foldr (*) 1 [1..n]
это совершенно идиоматическая конструкция, и по духу она гораздо ближе к использованию цикла, чем к явной рекурсии.
Процедурные языки имеют тенденцию отслеживать состояние (используя переменные) и имеют тенденцию выполняться как последовательность шагов. Чисто функциональные языки не отслеживают состояние, используют неизменяемые значения и имеют тенденцию исполняться в виде ряда зависимостей. Во многих случаях состояние стека вызовов будет содержать информацию, которая будет эквивалентна той, которая будет храниться в переменных состояния в процедурном коде.
Рекурсия - классический пример программирования функционального стиля.
Конрад сказал:
Как следствие, чисто функциональная программа всегда дает одно и то же значение для ввода, и порядок оценки не является четко определенным; Это означает, что неопределенные значения, такие как пользовательский ввод или случайные значения, трудно моделировать на чисто функциональных языках.
Порядок оценки в чисто функциональной программе может быть трудно (э-э) рассуждать (особенно с ленью) или даже неважно, но я думаю, что высказывание о том, что оно не очень хорошо определено, звучит так, как будто вы не можете определить, работает ли ваша программа. работать на всех!
Возможно, лучшим объяснением было бы то, что поток управления в функциональных программах основан на том, когда необходимо значение аргументов функции. Хорошая новость в том, что в хорошо написанных программах состояние становится явным: каждая функция перечисляет свои входные данные в качестве параметров, а не произвольно манипулирует глобальным состоянием. Таким образом, на некотором уровне легче рассуждать о порядке оценки по одной функции за раз . Каждая функция может игнорировать остальную часть вселенной и сосредоточиться на том, что ей нужно делать. При объединении функции гарантированно работают так же [1], как и в изоляции.
... неопределенные значения, такие как пользовательский ввод или случайные значения, трудно моделировать на чисто функциональных языках.
Решение проблемы ввода в чисто функциональных программах состоит в том, чтобы встроить императивный язык как DSL с использованием достаточно мощной абстракции . В императивных (или не чисто функциональных) языках это не нужно, потому что вы можете «обманывать» и неявно передавать состояние, а порядок вычисления является явным (нравится вам это или нет). Из-за этого «обмана» и принудительной оценки всех параметров для каждой функции в императивных языках 1) вы теряете возможность создавать свои собственные механизмы управления потоками (без макросов), 2) код не является поточно-ориентированным и / или распараллеливаемым по умолчанию, 3) и реализация чего-то вроде отмены (путешествия во времени) требует тщательной работы (императивный программист должен хранить рецепт для возврата старых значений!), Тогда как чистое функциональное программирование покупает вам все эти вещи - и еще несколько, я могу забыли - «бесплатно».
Надеюсь, это не похоже на фанатизм, я просто хотел добавить немного перспективы. Императивное программирование и особенно программирование со смешанной парадигмой в таких мощных языках, как C # 3.0, по-прежнему являются полностью эффективными способами добиться цели, и серебряной пули не существует .
[1] ... за исключением, возможно, относительно использования памяти (см. Foldl и foldl 'в Haskell).
Чтобы расширить комментарий Конрада:
и порядок оценки не является четко определенным
Некоторые функциональные языки имеют то, что называется Lazy Evaluation. Это означает, что функция не выполняется, пока значение не требуется. До этого момента сама функция - это то, что передается.
Процедурные языки - это шаг 1, шаг 2, шаг 3 ... если на шаге 2 вы скажете добавить 2 + 2, тогда это будет сделано правильно. В ленивой оценке вы сказали бы добавить 2 + 2, но если результат никогда не используется, он никогда не делает сложения.
Если у вас есть шанс, я бы порекомендовал получить копию Lisp / Scheme и выполнить в ней несколько проектов. Большинство идей, которые в последнее время стали популярными, были выражены в Лиспе десятилетия назад: функциональное программирование, продолжения (как замыкания), сборка мусора, даже XML.
Так что это был бы хороший способ получить представление о всех этих текущих идеях, а также о некоторых других, таких как символические вычисления.
Вы должны знать, для чего полезно функциональное программирование, а для чего - нет. Это не хорошо для всего. Некоторые проблемы лучше всего выражаются в виде побочных эффектов, когда один и тот же вопрос дает разные ответы в зависимости от того, когда его задают.
@Creighton:
В Haskell есть библиотечная функция под названием product :
prouduct list = foldr 1 (*) list
или просто:
product = foldr 1 (*)
так что "идиоматический" факториал
fac n = foldr 1 (*) [1..n]
будет просто
fac n = product [1..n]
Процедурное программирование делит последовательности операторов и условных конструкций на отдельные блоки, называемые процедурами, которые параметризуются по аргументам, которые являются (нефункциональными) значениями.
Функциональное программирование такое же, за исключением того, что функции являются первоклассными значениями, поэтому они могут передаваться в качестве аргументов другим функциям и возвращаться как результаты вызовов функций.
Обратите внимание, что функциональное программирование является обобщением процедурного программирования в этой интерпретации. Тем не менее, меньшинство интерпретирует «функциональное программирование» как означающее отсутствие побочных эффектов, что совершенно иное, но не имеет значения для всех основных функциональных языков, кроме Haskell.
Чтобы понять разницу, нужно понять, что парадигма «крестного отца» как процедурного, так и функционального программирования является императивным программированием. .
По сути, процедурное программирование - это просто способ структурирования императивных программ, в котором основным методом абстракции является «процедура». (или «функция» в некоторых языках программирования). Даже объектно-ориентированное программирование - это просто еще один способ структурирования императивной программы, где состояние инкапсулируется в объекты, превращаясь в объект с «текущим состоянием», плюс этот объект имеет набор функций, методов и других вещей, которые позволяют вам программист манипулирует или обновляет состояние.
Теперь, что касается функционального программирования, суть его подхода заключается в том, что он определяет, какие значения принимать и как эти значения следует передавать. (таким образом, нет состояния и изменяемых данных, поскольку он принимает функции в качестве значений первого класса и передает их в качестве параметров другим функциям).
PS: понимание каждой используемой парадигмы программирования должно прояснить различия между ними.
PSS: В конце концов, парадигмы программирования - это просто разные подходы к решению проблем.
PSS: у этого корального ответа есть отличное объяснение.
Ни один из ответов здесь не показывает идиоматическое функциональное программирование. Рекурсивный факторный ответ отлично подходит для представления рекурсии в 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
оптимизации).