Просто обычно неизменяемые типы, созданные в языках, которые не вращаются вокруг неизменности, будут иметь тенденцию тратить больше времени на разработку, а также потенциально использовать, если им потребуется какой-то объект типа «конструктор» для выражения желаемых изменений (это не означает, что в целом работы будет больше, но в этих случаях есть предоплата). Кроме того, независимо от того, позволяет ли язык действительно легко создавать неизменяемые типы или нет, он будет всегда требовать некоторой обработки и дополнительных затрат памяти для нетривиальных типов данных.
Создание функций, лишенных побочных эффектов
Если вы работаете на языках, которые не вращаются вокруг неизменности, то я думаю, что прагматический подход заключается не в стремлении сделать каждый тип данных неизменным. Потенциально гораздо более продуктивный образ мышления, который дает вам много таких же преимуществ, заключается в том, чтобы сосредоточиться на максимизации количества функций в вашей системе, которые вызывают нулевые побочные эффекты .
В качестве простого примера, если у вас есть функция, которая вызывает побочный эффект, подобный этому:
// 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действительно окупится, даже если он стоил немного дороже, потому что он не просто упростил безопасность потоков. Это также упростило неразрушающее редактирование (позволяя пользователю наслоить операции с сеткой без изменения его оригинальной копии), отменить системы (теперь система отмены может просто сохранять неизменную копию сетки до изменений, сделанных операцией, не разрушая память use) и безопасность исключений (теперь, если исключение возникает в вышеупомянутой функции, функция не должна откатываться и отменять все свои побочные эффекты, поскольку она не вызывала их с самого начала).
В этих случаях я могу с уверенностью сказать, что время, необходимое для того, чтобы сделать эти здоровенные структуры данных неизменяемыми, сэкономило больше времени, чем стоило, так как я сравнил затраты на обслуживание этих новых конструкций с прежними, которые вращались вокруг изменчивости и функций, вызывающих побочные эффекты, и прежние изменяемые конструкции стоили гораздо больше времени и были гораздо более подвержены человеческим ошибкам, особенно в тех областях, где разработчикам действительно хочется пренебречь во время кризиса, например, безопасность исключений.
Поэтому я считаю, что неизменяемые типы данных действительно оправдывают себя в этих случаях, но не все должно быть сделано неизменным, чтобы большинство функций в вашей системе было лишено побочных эффектов. Многие вещи достаточно дешевы, чтобы просто скопировать их полностью. Также многие приложения реального мира должны вызывать некоторые побочные эффекты здесь и там (по крайней мере, как сохранение файла), но обычно существует гораздо больше функций, которые могут быть лишены побочных эффектов.
Смысл в том, чтобы иметь несколько неизменных типов данных для меня, чтобы убедиться, что мы можем написать максимальное количество функций, чтобы избежать побочных эффектов, не неся эпических издержек в виде глубокого копирования массивных структур данных влево и вправо полностью, когда только небольшими порциями из них должны быть изменены. Наличие постоянных структур данных в этих случаях в конечном итоге превращается в деталь оптимизации, позволяющую нам писать наши функции без побочных эффектов, не затрачивая при этом огромных затрат.
Неизменные накладные расходы
Теперь концептуально изменяемые версии всегда будут иметь преимущество в эффективности. Всегда есть вычислительные затраты, связанные с неизменяемыми структурами данных. Но я нашел это достойным обменом в случаях, которые я описал выше, и вы можете сосредоточиться на том, чтобы сделать накладные расходы достаточно минимальными по своей природе. Я предпочитаю такой подход, когда правильность становится проще, а оптимизация становится сложнее, чем оптимизация легче, а правильность становится сложнее. Это не так уж и деморализующе - иметь код, который функционирует совершенно корректно и нуждается в дополнительных настройках кода, который в первую очередь работает некорректно, независимо от того, насколько быстро он достигает своих неверных результатов.