Чи існує незмінність у функціональному програмуванні насправді?


9

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

Моє розуміння незмінності:

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

Мої запитання:

  1. Якщо передбачається, що структури даних залишаться такими, якими вони є (незмінні), то як, пекло, додає новий елемент у список?
  2. Який сенс мати програму, яка не може отримати нові дані? Скажімо, у вас на комп’ютері підключений датчик, який хоче подавати дані в програму. Чи означає це, що ми не можемо зберігати вхідні дані ніде?
  3. Наскільки функціональне програмування добре для машинного навчання в цьому випадку? Оскільки машинне навчання базується на припущенні оновлення програми «сприйняття» речей - таким чином зберігаються нові дані.

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

1
Я думаю, що ваша плутанина полягає в тому, що ви думаєте про функціональні мови абстрактно. Просто візьміть будь-яку програму в Haskell - наприклад, програму, яка читає список номерів з консолі, швидко сортує її і виводить її - і з’ясовуйте, як вона працює і як вона спростовує ваші підозри. Немає способу дійсно зрозуміти речі, не дивлячись на приклади реальних програм, а не філософствуючи. Ви знайдете безліч програм у будь-якому навчальному посібнику Haskell.
jkff

@jkff Що ти намагаєшся сказати? Цей Хаскель має нефункціональні особливості. Питання не в Haskell, а в функціональному програмуванні. Або ти стверджуєш, що все це функціонально? Як? Отже, що має бути не так з філософствуванням, як ви кажете. Яким чином абстракція плутає? Питання щодо ОП - дуже розумне.
бабу

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

Один із способів поглянути на функціональне програмування - сказати, що це програмування без побічних ефектів. Це можна зробити на своїй "модній" мові на вибір. Просто не уникайте всіх перепризначень: наприклад, у Java всі ваші змінні будуть остаточними, а всі ваші методи - лише для читання.
reinierpost

Відповіді:


10

Коли програма запускається, вона має фіксовану структуру даних із фіксованими даними

Це трохи помилкове уявлення. Він має фіксовану форму і фіксований набір правил переписування, але ці правила перезапису можуть вибухнути в щось набагато більше. Наприклад, вираз [1..100000000] в Haskell представлений дуже невеликою кількістю коду, але його нормальна форма є масовою.

Не можна додати нові дані до цих структур

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

У коді немає змінних

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

Ви можете просто "скопіювати" з уже даних або наразі обчислених даних

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

Наприклад, "сума [1..1000]" не є розрахунком, який я хочу виконати, але це досить зручно робиться Haskell. Ми дали йому невеликий вираз, який мав для нас значення, і Haskell дав нам відповідне число. Тож він безумовно виконує розрахунок.

Якщо передбачається, що структури даних залишаться такими, якими вони є (незмінні), то як, пекло, додає новий елемент у список?

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

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

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

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

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


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

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

1
Я думаю, ви дещо неправильно представляєте ML. Так, введення / виведення може статися де завгодно, але спосіб введення нової інформації у існуючі структури жорстко контролюється.
dfeuer

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

1
Це правда, що існують алгоритми, які не мають чисто функціональної реалізації з такою ж складністю часу, як найкраща відома імперативна реалізація - наприклад, структура даних Union-Find (і, гм, масиви :)) Я думаю, що також існують такі випадки, як цей для простору складність. Але це винятки - найпоширеніші алгоритми / структури даних мають реалізації з рівною складністю часу та простору. Це суб'єктивна справа стилю програмування та (до постійного чинника) якості компілятора.
jkff

4

Незмінюваність або незмінність - це не поняття, які мають сенс у функціональному програмуванні.

Обчислювальний контекст

Це дуже гарне запитання, яке є цікавим поданням (а не дублікатом) до іншого недавнього: Яка різниця між призначенням, оцінкою та прив’язкою до імені?

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

Є кілька питань, на які слід відповісти, зокрема:

  • Що таке модель обчислення та які поняття мають сенс для даної моделі

  • Яке значення слів, які ви вживаєте, і як це залежить від контексту

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

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

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

Обчислювальні парадигми

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

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

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

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

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

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

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

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

Відповіді повинні бути точніше пов'язані з конкретною парадигмою, без зайвих.

Що таке змінна?

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

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

У функціональному програмуванні змінна має ту саму мету, яку вона виконує в математиці, як утримувач місць для певного значення, але вона повинна бути надана. У традиційному імперативному програмуванні цю роль насправді відіграє константа (не плутати з літералами, які визначаються значенням, вираженим позначеннями, характерними для даної області значень, такими як 123, true, ["abdcz", 3.14]).

Змінні будь-якого виду, а також постійні можуть бути представлені ідентифікаторами.

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

Мови програмування зазвичай дозволяють будувати більші об'єкти з менших мов.

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

Як читати програму

Програма є принципово абстрактним описом вашого алгоритму - якоюсь мовою, будь то прагматична конструкція чи парадигматично чиста мова.

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

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

Функціональне програмування та можливість зміни

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

Функціональне програмування має справу виключно зі значеннями.

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

У вас є лише чисті значення, а масив цілих чисел - це чисте значення. Щоб отримати інший масив, який відрізняється лише одним елементом, ви повинні використовувати інше значення масиву. Зміна елемента - це лише поняття, яке не існує в цьому контексті. Можливо, у вас є функція, яка містить масив та деякі індекси як аргумент, і повертає результат, який є майже ідентичним масивом, який відрізняється лише там, де зазначено індексами. Але це все-таки значення незалежного масиву. Як представлені ці значення - це не ваша проблема. Можливо, вони багато "діляться" в імперативному перекладі на комп’ютер ... але це робота компілятора ... і ви навіть не хочете знати, для якої архітектури машини вона збирається.

Ви не копіюєте значення (це не має сенсу, це чуже поняття). Ви просто використовуєте значення, які існують у областях, визначених у вашій програмі. Або ви описуєте їх (як буквальні), або вони є результатом застосування функції до деяких інших значень. Ви можете дати їм ім'я (таким чином визначивши константу), щоб переконатися, що одне і те ж значення використовується в різних місцях програми. Зауважте, що застосування функції не повинно сприйматися як обчислення, а як результат застосування до заданих аргументів. Написання 5+2чи написання 7становить те саме. Що узгоджується з попереднім пунктом.

Немає імперативних змінних. Призначення неможливо. Ви можете прив'язувати імена лише до значень (для формування констант), на відміну від імперативних мов, де ви можете прив’язувати імена до призначуваних змінних.

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

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

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

Відповідаючи на запитання

  1. Ви не змінюєте наявне значення (чужорідне поняття), але обчислюєте нові значення, які відрізняються, де потрібно, можливо, маючи один додатковий елемент, це список.

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

    Ви можете використовувати це для створення мереж комунікаційних компонентів суто прикладним способом (супроводи)

  3. Машинне навчання - це ще одна проблема, коли вам доведеться акредити дані та змінювати значення. У функціональному програмуванні ви цього не робите: ви просто обчислюєте нові значення, які відповідно відрізняються відповідно до навчальних даних. Отримана машина також буде працювати. Що вас хвилює - це обчислення ефективності часу та простору. Але, знову ж таки, це інше питання, яким в ідеалі повинен займатися компілятор.

Заключні зауваження

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

Чи можна писати в суто додатковому стилі? Відповідь відома вже близько 40 років, і це "так". Сама мета денотаційної семантики, як вона з'явилася в 1970-х, полягала саме в тому, щоб перекласти (скласти) мови в суто функціональний стиль, який вважався математично краще зрозумілим і, таким чином, вважався кращим фундаментом для визначення семантики програм.

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


0

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

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

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

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


"Функціональні програми, як і будь-які програми, дійсно функціонують зіставленням цілих чисел до цілих чисел." Як, скажімо, Minecraft, насправді функція відображення цілих чисел на цілі числа?
Девід Річербі

Легко. Кожен байт можна інтерпретувати як двійкове ціле число. Стан у комп'ютері - це сукупність байтів. Програма, навіть Minecraft, маніпулює станом комп'ютерів, відображаючи його з одного стану в інший.
Джеппе Хартмунд

Введення користувачів, схоже, не вписується в цей світ.
Девід Річербі,

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