Я читав статті у Вікіпедії як щодо процедурного програмування, так і для функціонального програмування , але все ще трохи розгублений. Чи може хтось зварити його до основи?
Я читав статті у Вікіпедії як щодо процедурного програмування, так і для функціонального програмування , але все ще трохи розгублений. Чи може хтось зварити його до основи?
Відповіді:
Функціональна мова (в ідеалі) дозволяє записати математичну функцію, тобто функцію, яка приймає 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 }
Я ніколи не бачив, щоб це визначення було подано в інших місцях, але я думаю, що це підсумовує відмінності, наведені тут, досить добре:
Функціональне програмування фокусується на виразах
Процедурне програмування фокусується на твердженнях
Вирази мають значення. Функціональна програма - це вираз, значення якого - це послідовність вказівок для виконання комп'ютером.
Заяви не мають значень і замість цього змінюють стан певної концептуальної машини.
У чисто функціональній мові не було б тверджень, в тому сенсі, що немає можливості маніпулювати станом (вони все ще можуть мати синтаксичну конструкцію під назвою "заява", але, якщо вона не маніпулює станом, я б не називав це заявою в цьому сенсі ). Чисто процедурною мовою не було б виразів, все було б інструкцією, яка маніпулює станом машини.
Haskell був би прикладом суто функціональної мови, оскільки немає можливості маніпулювати державою. Машинний код може бути прикладом чисто процедурної мови, оскільки все в програмі - це заява, яка маніпулює станом регістрів і пам'яті машини.
Заплутаною є те, що переважна більшість мов програмування містить як вирази, так і висловлювання, що дозволяє змішувати парадигми. Мови можна класифікувати як більш функціональні або більш процедурні залежно від того, наскільки вони заохочують використання висловлювань проти виразів.
Наприклад, C був би більш функціональним, ніж COBOL, оскільки виклик функції - це вираз, тоді як виклик підпрограми в COBOL - це вислів (який маніпулює станом загальних змінних і не повертає значення). Python був би більш функціональним, ніж C, оскільки він дозволяє висловити умовну логіку як вираз, використовуючи оцінку короткого замикання (тест && path1 || path2 на відміну від операторів if). Схема була б більш функціональною, ніж Python, тому що все в схемі є виразом.
Ви все ще можете писати у функціональному стилі мовою, яка заохочує процедурну парадигму і навпаки. Лише складніше і / або незручніше писати в парадигмі, яка не заохочена мовою.
В інформатиці функціональне програмування - це парадигма програмування, яка розглядає обчислення як оцінку математичних функцій і уникає даних про стан і змінні. Він підкреслює застосування функцій на відміну від стилю процедурного програмування, який підкреслює зміни у стані.
GetUserContext()
функцію, контекст користувача буде передано. Це функціональне програмування? Заздалегідь спасибі.
Я вважаю, що процедурне / функціональне / об'єктивне програмування - це як підійти до проблеми.
Перший стиль планував би все по кроках і вирішує проблему, впроваджуючи один крок (процедуру) за один раз. З іншого боку, функціональне програмування підкреслює підхід «ділити і перемагай», коли проблема поділяється на підпроблему, тоді кожна підпроблема вирішується (створюється функція для вирішення цієї підзадачі) і результати об'єднуються в створити відповідь на всю проблему. Нарешті, об'єктивне програмування імітує реальний світ, створюючи міні-світ всередині комп'ютера з багатьма об'єктами, кожен з яких має (дещо) унікальні характеристики, та взаємодіє з іншими. З цих взаємодій вийде результат.
Кожен стиль програмування має свої переваги та недоліки. Отже, зробити щось на кшталт "чистого програмування" (тобто чисто процедурне - ніхто, до речі, це не дивно - чи чисто функціонально чи чисто об'єктивно) дуже важко, якщо не неможливо, за винятком деяких елементарних проблем спеціально покликаний продемонструвати перевагу стилю програмування (отже, ми називаємо тих, хто любить чистоту, "weenie": D).
Тоді з цих стилів у нас є мови програмування, розроблені для оптимізації для кожного стилю. Наприклад, Асамблея стосується процедурних питань. Гаразд, більшість ранніх мов є процедурними, не тільки Asm, як C, Pascal, (і Fortran, я чув). Тоді у нас є відома Java в об'єктивній школі (насправді, Java і C # також є в класі, який називається "орієнтований на гроші", але це предмет для іншого обговорення). Також об'єктивним є Smalltalk. У функціональній школі у нас були б "майже функціональні" (деякі вважали їх нечистими) сімейство Лісп та сім'ї ML та багато "чисто функціональних" Haskell, Erlang тощо. До речі, існує багато загальних мов, таких як Perl, Python , Рубі.
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 .
Щоб розширити коментар Конрада:
Як наслідок, суто функціональна програма завжди дає однакове значення для вхідних даних, і порядок оцінювання недостатньо визначений;
Через це функціональний код, як правило, простіше паралелізувати. Оскільки побічних ефектів функцій (як правило) немає, і вони (як правило) просто діють на своїх аргументах, багато проблем з одночасністю відпадають.
Функціональне програмування також використовується, коли вам потрібно довести, що ваш код правильний. Це набагато складніше зробити з процедурним програмуванням (не просто з функціональним, але все ж простіше).
Відмова: Я не використовував функціональне програмування роками, і лише нещодавно знову почав його переглядати, тому я, можливо, тут не зовсім коректний. :)
Одне, чого я тут не бачив, - це те, що сучасні функціональні мови, такі як Haskell, справді більше стосуються функцій першого класу для управління потоком, ніж явні рекурсії. Вам не потрібно визначати факторию рекурсивно в Haskell, як це було зроблено вище. Я думаю, щось подібне
fac n = foldr (*) 1 [1..n]
це ідеально ідіоматична конструкція і набагато ближче за духом до використання циклу, ніж до використання явної рекурсії.
Процедурні мови, як правило, відстежують стан (використовуючи змінні) і прагнуть виконувати як послідовність кроків. Чисто функціональні мови не відстежують стан, використовують незмінні значення і прагнуть виконувати як ряд залежностей. У багатьох випадках статус стека викликів буде містити інформацію, яка була б еквівалентна тій, яка зберігалася б у змінних стану в процесуальному коді.
Рекурсія - це класичний приклад програмування функціонального стилю.
Конрад сказав:
Як наслідок, суто функціональна програма завжди дає однакове значення для вхідних даних, і порядок оцінювання недостатньо визначений; що означає, що невизначені значення, такі як введення користувача або випадкові значення, важко моделювати на суто функціональних мовах.
Порядок оцінювання в суто функціональній програмі може бути важко (помилково) міркувати (особливо з лінню) або навіть неважливо, але я вважаю, що, якщо сказати, що це не точно визначено, це звучить так, що ви не можете сказати, чи рухається ваша програма працювати взагалі!
Можливо, кращим поясненням буде те, що потік управління у функціональних програмах базується на тому, коли потрібні значення аргументів функції. Хороша річ у тому, що у добре написаних програмах стан стає явним: кожна функція перераховує свої вхідні дані як параметри замість довільного зміни глобального стану. Тож на якомусь рівні легше міркувати про порядок оцінювання стосовно однієї функції за раз . Кожна функція може ігнорувати решту Всесвіту і зосереджуватися на тому, що їй потрібно робити. У поєднанні функції гарантовано працюють так само [1], як і у відриві.
... невизначені значення, такі як введення користувача або випадкові значення, важко моделювати на суто функціональних мовах.
Рішення проблеми введення в суто функціональних програмах полягає у введенні імперативної мови як DSL з використанням досить потужної абстракції . В імперативних (або не чистих функціональних) мовах це не потрібно, тому що ви можете "обманювати" та передавати стан неявно, а порядок оцінки явний (вам це подобається чи ні). Через це "обман" і вимушене оцінювання всіх параметрів кожної функції, в обов'язкових мовах 1) ви втрачаєте можливість створювати власні механізми управління потоком (без макросів), 2) код по суті не є безпечним потоком та / або паралелізується за замовчуванням, 3) і реалізація чогось типу скасування (подорож у часі) вимагає ретельної роботи (обов'язковий програміст повинен зберігати рецепт повернення старого значення!), Тоді як чисте функціональне програмування купує вам усі ці речі - і ще декілька я можу забули - "безкоштовно".
Я сподіваюся, що це не схоже на завзяття, я просто хотів додати певну перспективу. Імперативне програмування і особливо змішане програмування парадигми на таких потужних мовах, як C # 3.0, як і раніше, є абсолютно ефективними способами зробити справи, і немає срібної кулі .
[1] ... крім випадків, що стосується використання пам'яті (пор. Foldl і foldl 'в Haskell).
Щоб розширити коментар Конрада:
і порядок оцінювання недостатньо визначений
Деякі функціональні мови мають те, що називається лінивою оцінкою. Що означає, що функція не виконується, поки не знадобиться значення. До цього часу сама функція - це те, що передається навколо.
Процедурні мови - це крок 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
це зроблено останнім часом. Використання Лодаша в JS, очевидно, не дає можливості оптимізувати, тому це враження продуктивності (Що зазвичай не стосується веб-розробки). Такі мови, як Rust, оптимізуватимуться всередині (І матимуть такі функції, як try_fold
сприяння && ret
оптимізації).