Чим відрізняється процедурне програмування від функціонального програмування? [зачинено]


247

Я читав статті у Вікіпедії як щодо процедурного програмування, так і для функціонального програмування , але все ще трохи розгублений. Чи може хтось зварити його до основи?


Вікіпедія означає, що FP - це підмножина декларативного програмування (тобто це завжди), але це неправда і суперечить таксономії IP проти DP .
Шелбі Мур III

Відповіді:


151

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

З іншого боку, процедурна мова виконує ряд послідовних кроків. (Існує спосіб перетворення послідовної логіки у функціональну логіку, яка називається стилем продовження проходження .)

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


1 Як і все в цій відповіді, це узагальнення. Ця властивість, оцінюючи обчислення, коли його результат потрібен, а не послідовно, де він називається, відомий як "лінь". Не всі функціональні мови насправді загалом ліниві, а також лінь не обмежується функціональним програмуванням. Швидше за все, описаний тут опис забезпечує "розумову основу" для роздумів про різні стилі програмування, які не є різними і протилежними категоріями, а досить текучими ідеями.


9
Невизначені значення, такі як введення користувача або випадкові значення, важко моделювати в суто функціональних мовах, але це вирішена проблема. Дивіться монади.
Апокаліпс

" послідовні кроки , де функціональна програма вкладеться" означає забезпечення роз'єднання проблем шляхом підкреслення складу функції , тобто розділення залежностей між підрахунками детермінованих обчислень.
Шелбі Мур III

це здається неправильним - процедури також можуть бути вкладені, процедури можуть мати параметри
Хурда

1
@Hurda Так, міг би це краще сформулювати. Справа в тому, що процедурне програмування відбувається поетапно в заздалегідь визначеному порядку, тоді як функціональні програми не виконуються поетапно; швидше, значення обчислюються, коли вони потрібні. Однак відсутність загальновизнаного визначення термінології програмування робить такі узагальнення поруч марними. Я змінив свою відповідь з цього приводу.
Конрад Рудольф

97

В основному два стилі, як Інь і Ян. Один організований, а інший хаотичний. Бувають ситуації, коли функціональне програмування є очевидним вибором, а інші ситуації, коли процедурне програмування є кращим вибором. Ось чому є щонайменше дві мови, які нещодавно вийшли з новою версією, яка охоплює обидва стилі програмування. ( Perl 6 і D 2 )

Процедурний:

  • Вихід з рутини не завжди має пряму кореляцію з входом.
  • Все робиться в певному порядку.
  • Виконання розпорядку може мати побічні ефекти.
  • Як правило, підкреслюють реалізацію рішень лінійно.

Перл 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;
}

Функціональні:

  • Часто рекурсивний.
  • Завжди повертає той самий вихід для заданого входу.
  • Порядок оцінювання зазвичай не визначений.
  • Повинно бути без громадянства. тобто жодна операція не може мати побічних ефектів.
  • Добре підходить для паралельного виконання
  • Як правило, підкреслює поділ та перемогу.
  • Може мати особливість Ледачої оцінки.

Хаскелл

(скопійовано з Вікіпедії );

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

Перл 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коли викликається без аргументів)


Привіт, ви можете надати приклад для наступних 2 пунктів, згаданих у "Процедурному", розглядаючи приклад факторної реалізації в Perl 6. 1) Вихід з рутини не завжди має пряму кореляцію з вхідними даними. 2) Виконання розпорядку може мати побічні ефекти.
Нага Кіран

sub postfix:<!> ($n) { [*] 1..$n }
Бред Гілберт

@BradGilbert No operation can have side effects-Чи можете ви докладно розробити це?
kushalvm

2
Напевно, найкраща відповідь, яку я коли-небудь міг знайти .... І я провів деякі дослідження з тих окремих пунктів .. що мені справді допомогло! :)
Navaneeth

1
@AkashBisariya sub foo( $a, $b ){ ($a,$b).pick }← не завжди повертає один і той же вихід на один і той же вхід, тоді як робить цеsub foo( $a, $b ){ $a + $b }
Бред Гілберт

70

Я ніколи не бачив, щоб це визначення було подано в інших місцях, але я думаю, що це підсумовує відмінності, наведені тут, досить добре:

Функціональне програмування фокусується на виразах

Процедурне програмування фокусується на твердженнях

Вирази мають значення. Функціональна програма - це вираз, значення якого - це послідовність вказівок для виконання комп'ютером.

Заяви не мають значень і замість цього змінюють стан певної концептуальної машини.

У чисто функціональній мові не було б тверджень, в тому сенсі, що немає можливості маніпулювати станом (вони все ще можуть мати синтаксичну конструкцію під назвою "заява", але, якщо вона не маніпулює станом, я б не називав це заявою в цьому сенсі ). Чисто процедурною мовою не було б виразів, все було б інструкцією, яка маніпулює станом машини.

Haskell був би прикладом суто функціональної мови, оскільки немає можливості маніпулювати державою. Машинний код може бути прикладом чисто процедурної мови, оскільки все в програмі - це заява, яка маніпулює станом регістрів і пам'яті машини.

Заплутаною є те, що переважна більшість мов програмування містить як вирази, так і висловлювання, що дозволяє змішувати парадигми. Мови можна класифікувати як більш функціональні або більш процедурні залежно від того, наскільки вони заохочують використання висловлювань проти виразів.

Наприклад, C був би більш функціональним, ніж COBOL, оскільки виклик функції - це вираз, тоді як виклик підпрограми в COBOL - це вислів (який маніпулює станом загальних змінних і не повертає значення). Python був би більш функціональним, ніж C, оскільки він дозволяє висловити умовну логіку як вираз, використовуючи оцінку короткого замикання (тест && path1 || path2 на відміну від операторів if). Схема була б більш функціональною, ніж Python, тому що все в схемі є виразом.

Ви все ще можете писати у функціональному стилі мовою, яка заохочує процедурну парадигму і навпаки. Лише складніше і / або незручніше писати в парадигмі, яка не заохочена мовою.


2
Найкраще та найсміливіше пояснення, яке я бачив у мережі Інтернет, браво!
приурочений

47

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


4
Хоча саме це пояснення допомогло мені найбільше, я все ще нечітко розумію концепцію функціонального програмування. Я шукаю стиль програмування, який не залежить від посилання на зовнішні об’єкти для запуску (кожна річ, яку потрібно виконати, повинна передаватися як параметр). Наприклад, я ніколи не ставлю GetUserContext()функцію, контекст користувача буде передано. Це функціональне програмування? Заздалегідь спасибі.
Метт Кашатт

26

Я вважаю, що процедурне / функціональне / об'єктивне програмування - це як підійти до проблеми.

Перший стиль планував би все по кроках і вирішує проблему, впроваджуючи один крок (процедуру) за один раз. З іншого боку, функціональне програмування підкреслює підхід «ділити і перемагай», коли проблема поділяється на підпроблему, тоді кожна підпроблема вирішується (створюється функція для вирішення цієї підзадачі) і результати об'єднуються в створити відповідь на всю проблему. Нарешті, об'єктивне програмування імітує реальний світ, створюючи міні-світ всередині комп'ютера з багатьма об'єктами, кожен з яких має (дещо) унікальні характеристики, та взаємодіє з іншими. З цих взаємодій вийде результат.

Кожен стиль програмування має свої переваги та недоліки. Отже, зробити щось на кшталт "чистого програмування" (тобто чисто процедурне - ніхто, до речі, це не дивно - чи чисто функціонально чи чисто об'єктивно) дуже важко, якщо не неможливо, за винятком деяких елементарних проблем спеціально покликаний продемонструвати перевагу стилю програмування (отже, ми називаємо тих, хто любить чистоту, "weenie": D).

Тоді з цих стилів у нас є мови програмування, розроблені для оптимізації для кожного стилю. Наприклад, Асамблея стосується процедурних питань. Гаразд, більшість ранніх мов є процедурними, не тільки Asm, як C, Pascal, (і Fortran, я чув). Тоді у нас є відома Java в об'єктивній школі (насправді, Java і C # також є в класі, який називається "орієнтований на гроші", але це предмет для іншого обговорення). Також об'єктивним є Smalltalk. У функціональній школі у нас були б "майже функціональні" (деякі вважали їх нечистими) сімейство Лісп та сім'ї ML та багато "чисто функціональних" Haskell, Erlang тощо. До речі, існує багато загальних мов, таких як Perl, Python , Рубі.


26

Функціональне програмування

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 .


5
цей приклад насправді простий для розуміння терміна "без громадянства" та "непорушних даних" у функціональному програмуванні, читаючи всі перелічені вище визначення та відмінності, не очистив мою плутанину до прочитання цієї відповіді. Дякую!
maximus

13

Щоб розширити коментар Конрада:

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

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

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

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


12

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

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

це ідеально ідіоматична конструкція і набагато ближче за духом до використання циклу, ніж до використання явної рекурсії.


10

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


7

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

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


1
Прочитавши цю сторінку, я думав про те саме -> "Рекурсія - класичний приклад програмування функціонального стилю", і ви її очистили. Дякую, Тепер я думаю, що я отримую якусь річ.
Мудассір Хуссейн

6

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

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

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

Можливо, кращим поясненням буде те, що потік управління у функціональних програмах базується на тому, коли потрібні значення аргументів функції. Хороша річ у тому, що у добре написаних програмах стан стає явним: кожна функція перераховує свої вхідні дані як параметри замість довільного зміни глобального стану. Тож на якомусь рівні легше міркувати про порядок оцінювання стосовно однієї функції за раз . Кожна функція може ігнорувати решту Всесвіту і зосереджуватися на тому, що їй потрібно робити. У поєднанні функції гарантовано працюють так само [1], як і у відриві.

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

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

Я сподіваюся, що це не схоже на завзяття, я просто хотів додати певну перспективу. Імперативне програмування і особливо змішане програмування парадигми на таких потужних мовах, як C # 3.0, як і раніше, є абсолютно ефективними способами зробити справи, і немає срібної кулі .

[1] ... крім випадків, що стосується використання пам'яті (пор. Foldl і foldl 'в Haskell).


5

Щоб розширити коментар Конрада:

і порядок оцінювання недостатньо визначений

Деякі функціональні мови мають те, що називається лінивою оцінкою. Що означає, що функція не виконується, поки не знадобиться значення. До цього часу сама функція - це те, що передається навколо.

Процедурні мови - це крок 1, крок 2, крок 3 ... якщо на кроці 2 ви говорите, додайте 2 + 2, він робить це правильно. У лінивій оцінці ви б сказали, що додайте 2 + 2, але якщо результат ніколи не використовується, він ніколи не додає.


4

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

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

Ви повинні знати, для чого функціональне програмування добре, а для чого воно не годиться. Це не добре для всього. Деякі проблеми найкраще виражаються в побічних ефектах, де те саме запитання дає різні відповіді залежно від того, коли його задають.


3

@Creighton:

У Haskell є функція бібліотеки під назвою product :

prouduct list = foldr 1 (*) list

або просто:

product = foldr 1 (*)

тому "ідіоматичний" факторіал

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

просто було б

fac n = product [1..n]

Це не дає відповіді на запитання. Щоб критикувати або вимагати роз'яснення у автора, залиште коментар під їх дописом.
Нік Кітто

Я вважаю, що це було розміщено багато років тому, до того, як система коментарів була додана, якщо ви можете повірити: stackoverflow.com/help/badges/30/beta?userid=2543
Jared Updike

2

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

Функціональне програмування те саме, за винятком того, що функції є першокласними значеннями, тому їх можна передавати як аргументи іншим функціям і повертати як результати викликів функцій.

Зауважимо, що функціональне програмування є узагальненням процедурного програмування в цій інтерпретації. Однак меншість інтерпретує "функціональне програмування" як побічний ефект, який є зовсім іншим, але не має значення для всіх основних функціональних мов, крім Haskell.


1

Щоб зрозуміти різницю, потрібно розуміти, що парадигмою хрещеного батька як процедурного, так і функціонального програмування є імперативне програмування .

В основному процедурне програмування - це лише спосіб структурування імперативних програм, в яких основним методом абстрагування є "процедура". (або "функція" в деяких мовах програмування). Навіть об'єктно-орієнтоване програмування - це лише ще один спосіб структурування імперативної програми, коли стан інкапсулюється в об'єкти, перетворюючись на об'єкт із "поточним станом", плюс цей об'єкт має набір функцій, методів та інших речей, які дозволять вам програміст маніпулює або оновлює стан.

Що стосується функціонального програмування, суть його підходу полягає в тому, що він визначає, які значення слід прийняти і як ці значення слід передати. (тому немає ніякого стану та даних, що змінюються, оскільки вони приймають функції як значення першого класу та передають їх як параметри іншим функціям).

PS: розуміння кожної парадигми програмування, для якої слід використати, повинно з’ясувати відмінності між усіма ними.

PSS: Зрештою, парадигми програмування - це просто різні підходи до вирішення проблем.

PSS: ця відповідь квори має чудове пояснення.


0

Жодна з відповідей тут не містить ідіоматичного функціонального програмування. Рекурсивна факторіальна відповідь чудово підходить для рекурсії у 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оптимізації).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.