Я, правда кажучи, упереджений як один, хто застосовує такі поняття в C ++ мовою та її природою, а також моїм доменом і навіть тим, як ми використовуємо цю мову. Але з огляду на ці речі, я думаю, що незмінні конструкції є найменш цікавим аспектом, коли мова йде про отримання переваг, пов'язаних з функціональним програмуванням, як-от безпека потоку, простота міркувань щодо системи, пошук більшого використання функцій (і пошук того, що ми можемо поєднуйте їх у будь-якому порядку без неприємних сюрпризів) тощо.
Візьмемо цей спрощений приклад C ++ (правда, не оптимізовано для простоти, щоб не бентежити себе перед будь-якими фахівцями з обробки зображень):
// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
{
Image dst(new_w, new_h);
for (int y=0; y < new_h; ++y)
{
for (int x=0; x < new_w; ++x)
dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
}
return dst;
}
Хоча реалізація цієї функції мутує місцевий (і тимчасовий) стан у вигляді двох змінних лічильників та тимчасового локального зображення для виведення, воно не має зовнішніх побічних ефектів. Він вводить зображення і виводить нове. Ми можемо його багатопрочитати до змісту серця. Це легко міркувати, легко ретельно перевірити. Це безпечно для винятків, оскільки якщо щось кинеться, нове зображення автоматично відкидається, і нам не потрібно турбуватися про відкат зовнішніх побічних ефектів (зовнішніх зображень не змінюється поза межами функції, так би мовити).
Я бачу, що мало що можна отримати, а потенційно багато чого втратити, зробивши Image
непорушним у вищезгаданому контексті C ++, за винятком того, що потенційно зробить вищезгадану функцію більш непростою для реалізації та, можливо, трохи менш ефективною.
Чистота
Тож чисті функції (без зовнішніх побічних ефектів) мені дуже цікаві, і я підкреслюю важливість часто віддавати перевагу членам команди навіть у С ++. Але незмінні конструкції, застосовані як правило, відсутні контекст і нюанс, не є для мене майже такими цікавими, оскільки, враховуючи імперативний характер мови, часто корисно і практично можна міняти деякі локальні тимчасові об'єкти в процесі ефективної роботи (обидва для розробника та апаратних засобів) реалізація чистої функції.
Дешеве копіювання потужних конструкцій
Другою найбільш корисною властивістю, яку я вважаю, є можливість дешево копіювати дійсно здорові структури даних, коли витрати на це, як це часто виникає, щоб зробити функції чистими, враховуючи їх суворий характер введення / виведення, були б нетривіальними. Це не будуть маленькі структури, які можуть вміститися на стеку. Вони були б великими, здоровенними структурами, як цілі Scene
для відеоігор.
У такому випадку копіювання накладних даних може запобігти можливостям ефективного паралелізму, оскільки може бути важко паралелізувати фізику та рендерінг, не замикаючи і вузько замикаючи один одного, якщо фізика мутує сцену, яку рендер одночасно намагається намалювати, одночасно маючи фізику глибокої Скопіюйте всю ігрову сцену навколо просто для виведення одного кадру із застосованою фізикою може бути однаково неефективним. Однак, якщо система фізики була "чистою" в тому сенсі, що вона просто вводила сцену і виводила нову із застосованою фізикою, і така чистота не прийшла ціною астрономічного копіювання накладних витрат, вона могла б безпечно працювати паралельно рендерінг, не чекаючи іншого.
Тож можливість дешевого копіювання дійсно здоровенних даних про стан вашої програми та виведення нових, модифікованих версій з мінімальними витратами на обробку та використання пам’яті справді може відкрити нові двері для чистоти та ефективного паралелізму, і там я знаходжу багато уроків від того, як реалізуються стійкі структури даних. Але все, що ми створюємо, використовуючи подібні уроки, не повинно бути повністю стійким або пропонувати незмінний інтерфейс (він може використовувати, наприклад, копіювання під час запису, або "будівельник / перехідний"), щоб досягти цієї здатності бути дешевим брудом. копіювати навколо та змінювати лише частини копії, не збільшуючи подвоєння використання пам'яті та доступу до пам'яті в нашому прагненні до паралелізму та чистоти наших функцій / систем / трубопроводу.
Незмінюваність
Нарешті, є незмінність, яку я вважаю найменш цікавою з цих трьох, але це може бути застосовано залізним кулаком, коли певні конструкції об'єктів не повинні використовуватися як локальні часописи до чистої функції, а натомість у більш широкому контексті - цінна своєрідна "чистота об'єктного рівня", оскільки у всіх методах більше не викликають зовнішніх побічних ефектів (більше не мутують змінних членів за межами безпосередньої локальної сфери методу).
І хоча я вважаю це найменш цікавим з цих трьох мов, таких як C ++, він, безумовно, може спростити тестування та безпеку потоку та міркування нетривіальних об'єктів. Працювати з гарантією того, що об'єкту не може бути надана будь-яка унікальна комбінація стану поза його конструктором, може бути, і це може бути завантаженням, наприклад, і що ми можемо вільно передавати його навколо, навіть за посиланням / покажчиком, не спираючись на невимушеність і читання- лише ітератори та ручки тощо, гарантуючи (ну, принаймні, наскільки ми можемо в рамках мови), що його оригінальний вміст не буде змінено.
Але я вважаю це найменш цікавим властивістю, тому що більшість об'єктів, які я вважаю такими вигідними, як тимчасово використовувані в змінній формі, реалізують чисту функцію (або навіть більш широку концепцію, як "чиста система", яка може бути об'єктом чи серією функціонує з кінцевим ефектом - просто вводити щось і виводити щось нове, не торкаючись чогось іншого), і я вважаю, що незмінність, взята до кінців, значною мірою необхідна мова, є досить контрпродуктивною метою. Я б застосував це порядно для частин бази даних, де це справді найбільше допомагає.
Нарешті:
[...] Здавалося б, стійкі структури даних самі по собі не є достатніми для обробки сценаріїв, коли один потік вносить зміни, видимі іншим потокам. Для цього, здається, ми повинні використовувати такі пристрої, як атоми, посилання, транзакційна пам'ять програмного забезпечення або навіть класичні блокування та механізми синхронізації.
Звичайно, якщо ваш дизайн вимагає, щоб зміни (у дизайнерському сенсі дизайну) були видимими для декількох потоків одночасно під час їх виникнення, ми повертаємося до синхронізації або, принаймні, креслення, щоб розробити деякі складні способи вирішення цього питання ( Я бачив кілька дуже детальних прикладів, які використовували фахівці, що займаються подібними проблемами функціонального програмування).
Але я виявив, що коли ви отримаєте такий тип копіювання та можливість вивести частково модифіковані версії здоровенних структур, що забруднюються дешево, як це можна отримати зі стійкими структурами даних, наприклад, це часто відкриває багато дверей та можливостей не замислювався раніше про паралелізацію коду, який може працювати повністю незалежно один від одного в строгому паралельному трубопроводі вводу / виводу. Навіть якщо деякі частини алгоритму мають мати послідовний характер, ви можете відкласти цю обробку на одну нитку, але виявите, що, спираючись на ці поняття, відкрив двері, щоб легко і без побоювань паралелізувати 90% здоровенної роботи, наприклад