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


9

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

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

Ось приклад закриття, написаного в Scala:

def filterList(xs: List[Int], lowerBound: Int): List[Int] =
  xs.filter(x => x >= lowerBound)

Літерал функції x => x >= lowerBoundмістить вільну змінну lowerBound, яка закрита (пов'язана) аргументом функції, filterListщо має те саме ім'я. Закриття передається методу бібліотеки filter, який може повторно викликати його як звичайну функцію.

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

Визначення функціонального програмування у вікіпедії звучить:

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

і далі

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

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

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

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

ВАЖЛИВА ПРИМІТКА

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

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


3
У чисто функціональному стилі / мові побічні ефекти неможливі ... тож я думаю, це було б питанням: на якому рівні чистоти можна вважати код функціональним?
Стівен Еверс

@SnOrfus: На 100%?
Джорджіо

1
Побічні ефекти неможливі чисто функціональною мовою, тому це повинно відповісти на ваше запитання.
Стівен Еверс

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

Чи може лишитель залишити записку про те, як поліпшити це питання?
Джорджіо

Відповіді:


7

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

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

Це, звичайно, дуже спрощений погляд.


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

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

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

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

@Giorgio: більшість класичних мов FP робити є мінливість; Haskell - це єдиний, про кого я можу придумати верх голови, який взагалі не дозволяє змінювати. Тим не менш, уникнення стану, що змінюється, є важливою цінністю FP.
tdammers

9

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

Щоб зрозуміти, чому, подивіться запис у блозі Еріка Ліпперта про ForEach<T>метод розширення, і чому Microsoft не включив такий метод послідовності, як він у Linq :

Я по-філософськи проти такого способу із двох причин.

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

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

foreach(Foo foo in foos){ statement involving foo; }

в цей код:

foos.ForEach((Foo foo)=>{ statement involving foo; });

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


1
Хоча, я згоден з ним ... його аргумент трохи слабшає при розгляді ParallelQuery<T>.ForAll(...). Реалізація такої IEnumerable<T>.ForEach(...)надзвичайно корисна для налагодження ForAllзаяви (замінити ForAllз ForEachі видалити , AsParallel()і ви можете набагато легше покроково / налагодити)
Стівен Evers

Очевидно, що у Джо Даффі є різні ідеї. : D
Роберт Харві

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

5
@Giorgio: Закриття не повинні бути чистими, щоб все ще вважатись закриттями. Однак вони повинні бути чистими, щоб вважати їх "функціональним стилем".
Роберт Харві

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

0

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

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

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

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

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