Чи забороняють функціональні мови програмування побічні ефекти?


10

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

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

Але, крім того, побічний ефект має інше визначення. Побічний ефект

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

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

Отже, нарешті, чи дозволяють мови функціонального програмування побічні ефекти чи ні?

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

Відповіді:


26

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

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

Рішення полягає в тому, щоб припинити використання побічних ефектів і кодувати ці ефекти у зворотному значенні . Різні мови мають різні системи ефектів. Наприклад, Haskell використовує монади для кодування певних ефектів, таких як мутація IO або State. Мови C / C ++ / Rust мають систему типів, яка може заборонити мутацію деяких значень.

Імперативною мовою print("foo")функція щось надрукує і нічого не поверне. У чистому функціональному мові, як Haskell, printфункція також приймає об'єкт, що представляє стан зовнішнього світу, і повертає новий об'єкт, що представляє стан після виконання цього виводу. Щось схоже на newState = print "foo" oldState. Я можу створити стільки нових держав зі старого стану, скільки мені подобається. Однак основна функція коли-небудь використовуватиметься лише одна. Тому мені потрібно послідовно стані з кількох дій, ланцюгуючи функції. Для друку foo barя можу сказати щось на кшталт print "bar" (print "foo" originalState).

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

Зауважте, що Haskell є єдиною часто використовуваною функціональною мовою, яка використовує цей маршрут. Інші функціональні мови в т.ч. сім'я Лісп, сім'я ML та новіші функціональні мови, такі як Scala, відштовхують, але дозволяють все-таки побічні ефекти - їх можна назвати імперативно-функціональними мовами.

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

Безгромадянство має низку практичних переваг, таких як підвищення розумності та масштабованості. Це дуже популярно для програм із веб-додатків. Будь-який стан зберігається зовні, у спільній базі даних. Це полегшує балансування завантаження: мені не потрібно приклеювати сеанси на певний сервер. Що робити, якщо мені потрібно більше серверів? Просто додайте ще одну, оскільки вона використовує ту саму базу даних. Що робити, якщо один сервер виходить з ладу? Я можу повторити будь-які очікувані запити на іншому сервері. Звичайно, все ще є стан - у базі даних. Але я зробив це явно і витяг, і міг би використовувати внутрішньо чистий функціональний підхід, якщо хочу.


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

3
@codebot, Не зовсім, на мій погляд. При правильному впровадженні побічні ефекти у функціональному програмуванні повинні відображатися у типі повернення функції. Наприклад, якщо функція може вийти з ладу (якщо певний файл не існує або не можна встановити з'єднання з базою даних), тип повернення функції повинен інкапсулювати помилку, а не викидати функцію виключення. Ознайомтесь із прикладом програмування, орієнтованого на залізницю ( fsharpforfunandprofit.com/posts/recipe-part2 ).
Аарон М. Ешбах

"... їх можна назвати імперативно-функціональними мовами.": Саймон Пейтон Джонс писав "... Haskell - найкраща в світі імперативна мова програмування".
Джорджо

5

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

Я думаю, що це допомагає проілюструвати різницю на прикладі.

a = b + c

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

Імперативною мовою (C, Java, Javascript та ін.) Цей рядок коду є лише кроком у процесі. Це нічого не говорить про фундаментальну природу будь-якої з цінностей. Це говорить нам, що в даний момент після цього рядка коду (але перед наступним рядком) aбуде дорівнює bплюс, cале він нічого не говорить про це aв більш широкому сенсі.

У мові декларацій (Haskell, Scheme, Excel тощо) цей рядок коду говорить набагато більше. Він встановлює інваріантний взаємозв'язок між aта іншими двома об'єктами, таким чином, що це завжди буде aрівним bплюсу c. Зауважте, що я включив Excel до списку мов декларацій, тому що навіть якщо bабо cзмінить значення, все одно залишиться факт, який aбуде дорівнює їх сумі.

На мій погляд, саме це , а не побічні ефекти або стан, - це те, що робить два типи мов різними. Ізитивною мовою будь-який конкретний рядок коду нічого не говорить про загальне значення змінних. Іншими словами, це a = b + cозначає лише те, що за дуже короткий момент часу aтрапилося рівну суму bі c.

Тим часом, у декларативних мовах кожен рядок коду встановлює фундаментальну істину, яка існуватиме протягом усього життя програми. Ці мови a = b + cговорять вам про те, що незалежно від того, що відбувається в будь-якому іншому рядку коду a, завжди буде дорівнювати сумі bі c.

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