Чому функціональні програми співвідносяться між успіхом компіляції та правильністю?


12

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

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

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

Хто-небудь замислювався над цим і придумав основну абстрактну причину співвідношення між успіхом компіляції та правильністю програми у функціональному програмуванні?


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

1
@delnan Я б не сказав, що Lisp - це функціональна мова програмування, хоча її можна використовувати для написання функціонального коду програмування. Clojure, що є діалектом Lisp, є функціональною мовою програмування
sakisk

2
Я згоден з @delnan. Це твердження більше пов'язане зі статистично типованими функціональними мовами програмування, особливо Haskell, який використовує систему Hindley-Milner. Я думаю, що головна ідея полягає в тому, що якщо ви правильно вкажете типи, підвищується впевненість у правильності вашої програми.
сакіск

1
Функціональний код може мати стільки ж абстракцій та непрямих, скільки і ваш типовий, основний код OOP (якщо не більше). Чорт у деталях - менше побічних ефектів і відсутність нульових значень менш невидимий стан для відстеження і менше шансів викрутитися. Зауважте, що ви можете застосовувати ті самі принципи в загальнообов'язкових імперативних мовах, це просто більше роботи і часто більш багатослівне (наприклад, потрібно ляпати finalпо всьому).
Довал

1
Не повна відповідь, але введення програми - це груба форма формальної перевірки програми. Загалом, об'єктно-орієнтовані програми мають або складні, або дуже прості типи систем, тому що заміни потрібно враховувати - у більшості випадків вони зроблені ненадійними заради зручності. OTOH ML-подібні системи можуть використовувати CH в повній мірі, тому ви можете кодувати доказ лише типом і використовувати компілятор як перевірку доказів.
Maciej Piechotka

Відповіді:


12

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

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

Однак, оскільки ми заплатили за трубопроводів заздалегідь, у нас набагато менше справи, і ми маємо набагато менше варіантів, як все може піти не так. Якщо ваш фанат 1984 року, свобода - це справді рабство! Використовуючи 101 різні акуратні трюки для програми, ми повинні міркувати про речі так, ніби будь-яка з цих 101 речі може статися! Це справді важко зробити, як виявляється :)

Якщо ви почнете з ножиць безпеки замість меча, біг помірно менш небезпечний.

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

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


1
Мені подобається ваша відповідь, але я не бачу, як вона відповідає на питання про ОП
sakisk

1
@faif Розширив мою відповідь. TLDR: всі - математики.
Даніель Гратцер

"Використовуючи 101 різні акуратні трюки для програми, ми повинні розмірковувати про речі так, ніби будь-яка з цих 101 речі може статися!": Я десь прочитав, що вам потрібно бути генієм, щоб програмувати мутацією, тому що ви повинні так тримати багато інформації в голові.
Джорджо

12

основна абстрактна причина співвідношення успіху компіляції та коректності програми у функціональному програмуванні?

Держава, що змінюється.

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

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

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

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

Напевно, це мікс обох з мого досвіду.


2
"Можливо, це мікс обох у моєму досвіді.": У мене однаковий досвід. Статична типізація виявляє помилки під час компіляції також при використанні обов'язкової мови (наприклад, Pascal). У FP уникнення змін і, додав би, використання більш декларативного стилю програмування полегшує міркування про код. Якщо мова пропонує обидва, ви отримуєте обидві переваги.
Джорджіо

7

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

for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        iterator.remove();
    }
}

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

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

Поєднайте функції першого класу, які дають вам можливість передавати присудок як параметр, з обмеженням незмінності, що робить його дуже прикро, якщо ви цього не робите, і ви придумуєте прості будівельні блоки filter, як у цьому коді Scala це робить те саме:

list filter (!_.isEmpty)

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

  • listповинен бути якийсь тип, який підтримує filterметод, а саме колекцію.
  • Елементи listmust повинні мати isEmptyметод, який повертає булеву форму.
  • Вихід буде (потенційно) меншою колекцією з однотипними елементами.

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

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

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


"Після перевірки цих речей, які інші способи залишається програмісту закрутити?": Це якимось чином підтверджує мій досвід, що (1) статичний набір тексту (+) функціональний стиль залишає менше способів викрутити речі. Як результат, я, як правило, отримую правильну програму швидше і мені потрібно писати менше одиничних тестів при використанні FP.
Джорджіо

2

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

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

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

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


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

2

Пояснення для менеджерів:

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

Процедурна програма - це як будівля з приміщеннями, що містять невелику машинку, зберігають часткові продукти в бункерах, отримують часткові продукти з інших місць. [Завод]

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


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

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


1
Краще анологія: функціональне програмування - це як довідкова служба без клієнтів - все чудово (до тих пір, поки ви не сумніваєтесь у меті чи ефективності).
Брендан

@Brendan car & factory не має такої поганої аналогії. Він намагається пояснити, чому (невеликі за масштабом) програми на функціональній мові швидше працюють і мають меншу ступінь помилок, ніж "фабрика". Але на допомогу кажуть, що OOP приходить, що фабрика може виробляти кілька речей і більше. Ваше порівняння влучне; як часто хтось чує FP, можна паралелізувати та оптимізувати дуже, але фактично (без каламбуру) дає повільні результати. Я все ще тримаюсь ФП.
Joop Eggen

Функціональне програмування в масштабі працює досить добре для en.wikipedia.org/wiki/Spherical_cow Тримайте його локальним.
День

@День я сам побоювався б, щоб не було проблем з техніко-економічним обґрунтуванням, працюючи над масштабним проектом ПП. Навіть люблять це. Узагальнення має своє обмеження. Але оскільки останнє твердження теж є узагальненням (спасибі за сферичну корову)
Joop Eggen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.