Переваги програмування без громадянства?


132

Нещодавно я дізнався про функціональне програмування (зокрема Haskell, але я також пройшов підручники на Lisp та Erlang). Хоча я вважав, що ці поняття є дуже освічуючими, я все ще не бачу практичної сторони концепції "без побічних ефектів". Які практичні переваги від цього? Я намагаюся думати у функціональному мисленні, але є деякі ситуації, які просто здаються надмірно складними без можливості легкого збереження стану (я не вважаю монади Хаскелла «легкими»).

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

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

Відповіді:


168

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

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

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


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

@Ray: Я також додав би розподілене програмування!
Антон Тихий

Більшість це правда, за винятком налагодження. Як правило, у haskell складніше, оскільки у вас немає справжнього стека викликів, а лише стек відповідності шаблону. І набагато складніше передбачити, чим закінчується ваш код.
hasufell

3
Крім того, функціональне програмування насправді не стосується "без громадянства". Рекурсія - це вже неявний (локальний) стан, і це головне, що ми робимо в haskell. Це стає зрозумілим, коли ви реалізуєте декілька нетривіальних алгоритмів в ідіоматичному хешкелі (наприклад, обчислювальна геометрія) та весело налагодите їх.
hasufell

2
Не подобається прирівнювати стан без громадянства до ПП. Багато програм ПП наповнені станом, воно просто існує в закритті, а не в об'єкті.
mikemaccana

46

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

Ви можете знайти хороший підручник з великою кількістю прикладів у статті Джона Х'юза Чому питання функціонального програмування (PDF).

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


Чи не могли б mixins також надавати код для багаторазового використання аналогічно OOP? Не виступаючи за ООП, просто намагаюся зрозуміти речі.
mikemaccana

20

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

Я фактично знаходжу (з особистого досвіду), що програмування на F # відповідає тому, як я вважаю краще, і тому це легше. Я думаю, що це найбільша різниця. Я програмував і в F #, і в C #, і в F # набагато менше "боротьби з мовою", яку я люблю. Не потрібно думати про деталі у F #. Ось кілька прикладів того, що я виявив, що мені дуже подобається.

Наприклад, навіть незважаючи на те, що F # статично набраний (усі типи вирішуються під час компіляції), умовиводи визначають, які типи у вас є, тому вам не доведеться цього говорити. І якщо він не може це зрозуміти, він автоматично робить вашу функцію / клас / будь-яку загальну. Тож ніколи не потрібно писати будь-якого загального, все автоматично. Я вважаю, що це означає, що я витрачаю більше часу на роздуми над проблемою і менше, як її втілити. Насправді, кожного разу, коли я повертаюся до C #, я виявляю, що мені дуже не вистачає такого висновку, ти ніколи не усвідомлюєш, наскільки це відволікає, поки тобі більше не потрібно робити цього.

Також у F # замість написання циклів ви викликаєте функції. Це тонка зміна, але значна, тому що вам більше не потрібно думати про контурну конструкцію. Наприклад, ось фрагмент коду, який би перейшов і щось збіг (не можу пригадати, це з головоломки проекту Ейлера):

let matchingFactors =
    factors
    |> Seq.filter (fun x -> largestPalindrome % x = 0)
    |> Seq.map (fun x -> (x, largestPalindrome / x))

Я усвідомлюю, що зробити фільтр, то карту (це перетворення кожного елемента) в C # було б досить просто, але ви повинні думати на нижчому рівні. Зокрема, вам доведеться написати цикл і мати власний явний if оператор та подібні речі. З часу вивчення F # я зрозумів, що мені легше кодувати функціональний спосіб, де, якщо ви хочете фільтрувати, ви пишете "filter", а якщо ви хочете зробити карту, ви пишете "map", а не реалізовувати кожна деталь.

Я також люблю оператор |>, який, на мою думку, відокремлює F # від ocaml і, можливо, інші функціональні мови. Це оператор труби, він дозволяє вам "передати" висновок одного виразу на вхід іншого виразу. Це змушує код слідувати, як я більше думаю. Як і у фрагменті коду, наведеному вище, така приказка: "візьміть послідовність факторів, відфільтруйте її, а потім відмітьте її". Це дуже високий рівень мислення, який ви не отримуєте в обов'язковій мові програмування, тому що ви настільки зайняті написанням циклу і якщо заяви. Це одне, що мені найбільше не вистачає, коли я переходжу на іншу мову.

Так що загалом, навіть якщо я можу програмувати як на C #, так і на F #, мені легше використовувати F #, оскільки ви можете думати на більш високому рівні. Я заперечую, що оскільки менші деталі вилучаються з функціонального програмування (принаймні в F #), я є більш продуктивним.

Редагувати : Я бачив в одному з коментарів, що ви просили приклад "стану" у функціональній мові програмування. F # можна записати в обов’язковому порядку, тому ось прямий приклад того, як можна змінити стан змін у F #:

let mutable x = 5
for i in 1..10 do
    x <- x + i

1
Я погоджуюсь із вашим дописом, але | | не має нічого спільного з функціональним програмуванням. Власне, a |> b (p1, p2)це просто синтаксичний цукор для b (a, p1, p2). З'єднайте це з правою асоціативністю, і ви це зрозуміли.
Антон Тихий

2
Щоправда, я повинен визнати, що, мабуть, багато мого позитивного досвіду роботи з F # має більше спільного з F #, ніж з функціональним програмуванням. Але все-таки існує сильна кореляція між цими двома, і хоча такі речі, як висновок типу та |>, не є функціональним програмуванням, само собою, я б сказав, що вони "йдуть з територією". Принаймні загалом.
Рей Хідаят

|> - це лише інша функція виправлення вищого порядку, в даному випадку - оператор додатка функції. Визначення власних операторів вищого порядку, infix-оператори, безумовно, є частиною функціонального програмування (якщо ви не Schemer). У Haskell є свій $, який однаковий, за винятком того, що інформація в трубопроводі протікає праворуч ліворуч.
Норман Рамзі

15

Розглянемо всі складні помилки, які ти довго проводив налагоджуючи.

Тепер, скільки цих помилок було пов'язано з "ненавмисною взаємодією" між двома окремими компонентами програми? (Майже всі помилки з нарізкою мають таку форму: перегони, що включають запис спільних даних, тупикові місця, ... Крім того, звичайно знаходити бібліотеки, які мають деякий несподіваний вплив на глобальний стан, або читати / записувати реєстр / середовище тощо) I могло б стверджувати, що принаймні 1 з 3 «важких помилок» потрапляє до цієї категорії.

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

Це великий виграш від ІМО "незмінність". В ідеальному світі ми б усі розробляли приголомшливі API-інтерфейси, і навіть коли все було б мінливим, ефекти були б локальними і добре задокументованими, а «несподівані» взаємодії зведені до мінімуму. У реальному світі існує безліч API, які взаємодіють із глобальним станом по-різному, і це джерело найбільш згубних помилок. Прагнення до безгромадянства прагне позбутися ненавмисної / неявної / закулісної взаємодії між компонентами.


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

8

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

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

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


Ця перша частина - це дійсно цікавий момент, я ніколи раніше про це не думав. Дякую!
Саша Чедигов

Припустимо, sin(PI/3)у вашому коді, де PI є постійною, компілятор міг би оцінити цю функцію під час компіляції та вбудувати результат у створений код.
Артелій

6

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


Так, я неодмінно розглядав це. Зокрема, модель інтерактивності Ерланга дуже інтригуюча. Однак на даний момент я не дуже хвилююся одночасною конкуренцією, а не продуктивністю. Чи існує премія за продуктивність від програмування без стану?
Саша Чедігов

2
@musicfreak, немає бонусу за продуктивність. Але на замітку, сучасні мови FP все ще дозволяють вам використовувати державні, якщо вам це справді потрібно.
Невідомо

Дійсно? Чи можете ви навести приклад стану у функціональній мові, просто щоб я бачив, як це робиться?
Саша Чедигов

Перевірте держава Монада в Haskell - book.realworldhaskell.org/read/monads.html#x_NZ
Ремпіон

4
@ Невідомо: Я не згоден. Програмування без стану зменшує виникнення помилок, які виникають через непередбачених / ненавмисних взаємодій різних компонентів. Це також заохочує до кращого дизайну (більше використання, розмежування механізму та політики та ін.). Це не завжди доречно для виконання завдання, але в деяких випадках справді світить.
Артелій

6

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

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

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

Кращим підходом є зберігання сеансу за веб-серверами в якомусь сховищі даних; в наші дні для цього доступно безліч чудових продуктів noql (redis, mongo, elasesearch, memcached). Таким чином, веб-сервери без стану, але у вас все ще є стан сервера, і доступністю цього стану можна керувати, вибравши правильну настройку сховища даних. Ці сховища даних зазвичай мають надлишкові надмірності, тому майже завжди має бути можливість вносити зміни до вашого веб-додатку та навіть сховища даних, не впливаючи на користувачів.


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