Просто загальнозмінні типи, створені мовами, які не обертаються навколо непорушності, як правило, коштуватимуть більше часу для розробника, а також потенційно використовувати, якщо вони вимагають певного типу "будівельника" для вираження бажаних змін (це не означає, що загальна роботи буде більше, але в цих випадках випереджає вартість). Крім того, незалежно від того, чи робить мову дійсно простим у створенні непорушних типів чи ні, воно, як правило, завжди потребує певної обробки та накладних даних для нетривіальних типів даних.
Зміна функцій, позбавлених побічних ефектів
Якщо ви працюєте мовами, які не обертаються навколо непорушності, то я думаю, що прагматичний підхід полягає не в тому, щоб прагнути зробити кожен тип даних незмінним. Потенційно набагато більш продуктивний спосіб мислення, який дає вам багато однакових переваг, - зосередитись на максимізації кількості функцій у вашій системі, що викликають нульові побічні ефекти .
Як простий приклад, якщо у вас є функція, яка викликає такий побічний ефект:
// Make 'x' the absolute value of itself.
void make_abs(int& x);
Тоді нам не потрібен непорушний цілий тип даних, який забороняє таким операторам, як призначення після ініціалізації, щоб ця функція уникала побічних ефектів. Ми можемо просто зробити це:
// Returns the absolute value of 'x'.
int abs(int x);
Тепер функція не возиться x
ні з якоюсь сферою, і в цьому тривіальному випадку ми могли б навіть поголити кілька циклів, уникаючи будь-яких накладних витрат, пов'язаних з непрямими / згладжуванням. Принаймні, друга версія не повинна бути обчислювально дорожчою, ніж перша.
Речі, які дорого копіюються в повному обсязі
Звичайно, більшість випадків не є такою дрібницею, якщо ми хочемо уникати того, щоб функція викликала побічні ефекти. Складний випадок використання в реальному світі може бути більше подібний:
// Transforms the vertices of the specified mesh by
// the specified transformation matrix.
void transform(Mesh& mesh, Matrix4f matrix);
У цей момент для сітки може знадобитися кілька сотень мегабайт пам’яті з понад сто тисяч полігонів, ще більше вершин і країв, декілька карт текстури, морфійські мішені тощо. Копіювати цю цілу сітку було б дуже дорого, щоб зробити це transform
функціонують без побічних ефектів, як-от так:
// Returns a new version of the mesh whose vertices been
// transformed by the specified transformation matrix.
Mesh transform(Mesh mesh, Matrix4f matrix);
І саме в тих випадках, коли копіювання чогось у повному обсязі, як правило, буде епічним накладним, коли я вважаю за корисне перетворити Mesh
на стійку структуру даних та незмінний тип із аналогічним "будівельником", щоб створити модифіковані версії його, щоб воно може просто неглибоко копіювати та посилатися на кількість частин, які не є унікальними. Це все з фокусом на можливості писати сітчасті функції, які не мають побічних ефектів.
Стійкі структури даних
І в тих випадках, коли копіювати все настільки неймовірно дорого, я виявив зусилля, щоб створити незмінний, Mesh
щоб справді окупитися, навіть якщо він мав трохи круту вартість вперед, оскільки це не просто спростило безпеку ниток. Це також спростило неруйнівне редагування (дозволяючи користувачеві здійснювати шарові операції з сіткою без зміни його оригінальної копії), скасовує системи (тепер система скасування може просто зберігати незмінну копію сітки до змін, внесених операцією, не вибухаючи пам'ять використання) та безпека винятків (тепер, якщо виняток виникає у наведеній вище функції, функція не повинна відкочуватись та скасовувати всі її побічні ефекти, оскільки вона не викликала жодного початку).
Я можу впевнено сказати в цих випадках, що час, необхідний для того, щоб зробити ці здоровенні структури даних незмінними, економив більше часу, ніж це коштувало, оскільки я порівняв витрати на обслуговування цих нових конструкцій з колишніми, які оберталися навколо змінності та функцій, що викликають побічні ефекти, і колишні змінні конструкції коштували набагато більше часу і були набагато більш схильні до людських помилок, особливо в тих областях, які справді спокушають розробників знехтувати під час стиснення, як-от безпека винятків.
Тому я думаю, що непорушні типи даних справді окупаються в цих випадках, але не все повинно бути непорушним, щоб звільнити більшість функцій у вашій системі без побічних ефектів. Багато речей досить дешеві, щоб просто копіювати повністю. Також багатьом реальним програмам потрібно буде викликати тут і там деякі побічні ефекти (принаймні, як збереження файлу), але зазвичай є набагато більше функцій, які можуть бути позбавлені побічних ефектів.
Сенс у мене деяких непорушних типів даних полягає в тому, щоб переконатися, що ми можемо записати максимальну кількість функцій, щоб бути вільними від побічних ефектів без виникнення епічних накладних даних у вигляді глибокого копіювання масивних структур даних ліворуч і праворуч у повному обсязі лише за невеликих порцій їх потрібно модифікувати. Постійні структури даних у цих випадках згодом стають оптимізаційними деталями, що дозволяють нам писати наші функції, щоб бути без побічних ефектів, не платячи за це епічні витрати.
Незмінний накладний
Тепер концептуально модифіковані версії завжди матимуть перевагу в ефективності. Завжди є обчислювальні витрати, пов'язані з незмінними структурами даних. Але я вважав його гідним обміном у вищеописаних випадках, і ви можете зосередитися на тому, щоб накладні витрати були настільки мінімальними. Я віддаю перевагу такому типу підходу, коли правильність стає легкою та оптимізація стає складнішою, ніж оптимізація стає легшою, але правильність стає жорсткішою. Мало того, що деморалізувати наявність коду, який функціонує ідеально правильно, потребуючи ще декількох налаштувань над кодом, який функціонує не в першу чергу, незалежно від того, наскільки швидко він досягає невірних результатів.