Чи функціональне програмування мінливими змінними, що не мають побічних ефектів, все ще вважаються «поганою практикою»?


23

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

наприклад, у курсі стилю курсу "Функціональне програмування з Scala" будь-яке varвикористання вважається поганим

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

наприклад, замість використання хвостової рекурсії з малюнком акумулятора, що поганого в тому, щоб зробити локальний цикл і створити локальний змінний ListBufferі додати до нього, доки вхід не буде змінено?

Якщо відповідь "так, вони завжди відлякують, навіть якщо побічних ефектів немає", то в чому причина?


3
Всі поради, зауваження тощо щодо теми, яку я коли-небудь чув, стосуються загального стану, що змінюється, як джерела складності. Цей курс призначений для споживання тільки початківцям? Тоді це, мабуть, цілеспрямоване навмисне спрощення.
Кіліан Фот

3
@KilianFoth: Спільний стан, що змінюється, є проблемою в багатопотокових контекстах, однак стан, що не змінюється, може не спричинити, що програми теж важко міркувати.
Майкл Шоу

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

Відповіді:


17

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

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

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


16

Проблема полягає не у змінності, а відсутності прозорості.

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

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

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


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

@CA McCann: Я думаю, що те, що ви говорите, дуже важливе: у функціональній мові час виконання може використовувати мутації для оптимізації обчислень, але в мові немає конструкції, яка б програмісту могла використовувати мутацію. Інший приклад - цикл час зі змінною циклу: в Haskell можна написати хвостову рекурсивну функцію, яка може бути реалізована зі змінною змінною (щоб уникнути використання стека), але те, що бачить програміст, - це аргументи незмінної функції, передані з одного дзвоніть до наступного.
Джорджіо

@Michael Shaw: +1 для "Проблема сама по собі не змінюється, це недостатня прозорість референції". Можливо, ви можете навести чисту мову, у якій є унікальні типи: вони дозволяють змінювати, але все ж гарантують референтну прозорість.
Джорджіо

@Giorgio: Я насправді нічого не знаю про Clean, хоча час від часу чую, що це згадувалося. Можливо, мені слід було б заглянути в це.
Майкл Шоу

@Michael Shaw: Я не знаю дуже багато про Clean, але знаю, що він використовує унікальні типи для забезпечення прозорості референції. В основному, ви можете змінювати об'єкт даних за умови, що після модифікації у вас немає посилань на старе значення. ІМО це ілюструє вашу думку: прозорість референції є найважливішим моментом, а незмінність - лише один із можливих способів її забезпечення.
Джорджіо

8

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

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

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

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


2
Так, мені майже ніколи не потрібна петля під час використання колекцій Scala. map, filter, foldLeftІ forEach зробити трюк більшу частину часу, але коли вони не роблять, будучи в стані почувати , що я «OK» , щоб повертатися до грубої сили імперативного коду добре. (доки, звичайно, немає побічних ефектів)
Еран Медан,

3

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

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

У функціоналі ви маєте сукупність, підрахунок, фільтр, пошук, flatMap, складку, groupBy, lastIndexWhere, карта, maxBy, minBy, розділ, сканування, сортуванняBy, sortWith, span та takeWhile, лише щоб назвати ще кілька поширених з Scala's стандартна бібліотека. Коли ви звикли мати доступні, імперативні forпетлі здаються занадто елементарними в порівнянні.

Єдиною реальною причиною використання локальної змінності є дуже інколи для виконання.


2

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

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

Як зазначається в посиланні:

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


2

Немає локальних змінних змінних має одну перевагу - це робить функцію більш зручною щодо потоків.

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

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