Що таке функціональне програмування?

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

У цьому розділі:

Стилі програмування

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

  • Більшість мов програмування є процедурними: програми є списками інструкцій, що вказують комп'ютеру, що робити з вхідними даними програми. C, Pascal, і навіть Unix shell є процедурними мовами.
  • У декларативних мовах ви пишете специфікацію, яка описує проблему, яку потрібно вирішити, і реалізація мови визначає, як виконати обчислення ефективно. SQL — це декларативна мова, з якою ви, швидше за все, знайомі; запит SQL описує набір даних, який ви хочете отримати, а SQL-двигун вирішує, чи сканувати таблиці або використовувати індекси, які підзапити виконувати першими тощо.
  • Об'єктно-орієнтовані програми маніпулюють колекціями об'єктів. Об'єкти мають внутрішній стан і підтримують методи, які запитують або змінюють цей внутрішній стан певним чином. Smalltalk і Java є об'єктно-орієнтованими мовами. C++ і Python — це мови, що підтримують об'єктно-орієнтоване програмування, але не змушують використовувати об'єктно-орієнтовані можливості.
  • Функціональне програмування розкладає проблему на набір функцій. Ідеально, функції беруть лише вхідні дані і виробляють виходи, і не мають жодного внутрішнього стану, який впливає на вихід, що виробляється для даного вхідного значення. Добре відомі функціональні мови включають сімейство ML (Standard ML, OCaml та інші варіанти) і Haskell.

Дизайнери деяких комп'ютерних мов вибирають підкреслювати один конкретний підхід до програмування. Це часто ускладнює написання програм, які використовують інший підхід. Інші мови є багатопарадигмальними мовами, які підтримують кілька різних підходів. Lisp, C++ і Python є багатопарадигмальними; ви можете написати програми або бібліотеки, які в основному є процедурними, об'єктно-орієнтованими або функціональними у всіх цих мовах. У великій програмі різні розділи можуть бути написані з використанням різних підходів; наприклад, GUI може бути об'єктно-орієнтованим, тоді як логіка обробки є процедурною або функціональною.

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

Деякі мови дуже суворі щодо чистоти і навіть не мають операторів присвоєння, таких як a = 3 або c = a + b, але важко уникнути всіх побічних ефектів, таких як виведення на екран або запис у файл на диску. Інший приклад — виклик функцій print або time.sleep, жодна з яких не повертає корисного значення. Обидві викликаються лише для їхніх побічних ефектів — відправки тексту на екран або припинення виконання на секунду.

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

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

Функціональний дизайн може здатися дивним обмеженням для роботи. Чому вам слід уникати об'єктів і побічних ефектів? Є теоретичні та практичні переваги функціонального стилю:

  • Формальна довідність.
  • Модульність.
  • Композиційність.
  • Легкість налагодження і тестування.

Формальна довідність

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

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

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

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

На жаль, доведення правильності програм є в основному непрактичним і не відноситься до програмного забезпечення на Python. Навіть тривіальні програми вимагають доказів, які займають кілька сторінок; доказ правильності помірно складної програми був би величезним, і мало або жодне з програм, які ви використовуєте щоденно (інтерпретатор Python, ваш XML-парсер, ваш веб-браузер) не могло б бути доведено правильним. Навіть якщо ви записали або згенерували доказ, то виникло б питання перевірки доказу; можливо, в ньому є помилка, і ви помилково вважаєте, що довели правильність програми.

Модульність

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

Легкість налагодження і тестування

Тестування і налагодження програми у функціональному стилі є простішими.

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

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

Композиційність

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

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

Текст на цій сторінці є перекладом "Functional Programming HOWTO", автор: A. M. Kuchling. Інформація про копірайт: History and License.