Взламываем принцип иммутабельности

После мозгового штурма и бессонной ночи учёный придумывает гениальную идею, которая перевернёт весь мир. Он лихорадочно пишет её на доске, после чего окружает крупными надписями «ПОЖАЛУЙСТА НЕ УДАЛЯЙТЕ ЭТО!!!», и выходит за чашечкой кофе. Через пять минут возвращается и с ужасом видит, что заглянувший на кафедру аспирант тщательно протирает доску, стирая с неё последние следы суперидеи.

Учёный кричит:

— В-в-вы разве не видели, что тут было написано крупными буквами???

— Это то, где говорилось «не удалять это»? — уточняет аспирант.

— Да!

— О… Я подумал, что это относится к самой доске — я не понимал, что её содержание тоже важно.

 

В Python есть тип данных «кортеж» (tuple), который аналогичен списку с той только разницей, что список мутабелен — он (его содержимое) может меняться в процессе вычислений, а кортеж — гарантируется! — иммутабелен (предполагается, что его значение всегда одно и то же, и изменить его невозможно).

 

Но вот простейший пример:

class Idea:
    power = 0
    def __init__(self, p):
        self.power = p

x = Idea(100)
tpl = (x,)
tpl[0].power # 100 

x.power = 1
tpl[0].power # 1 WTF ???

 

В кортеж мы добавили один элемент (объект), и так как переменная x остаётся мутабельной (она хранит ссылку на этот объект), у нас остаётся к нему прямой доступ. Несложно проверить, что это один и тот же объект:

 

id(x) == id(tpl[0]) # True

 

Схожая ситуация и в других языках, в JavaScript например, где можно вызывать Object.freeze(). При этом «замораживается» и сам объект, и даже внешние ссылки на него. Однако его внутренности (гениальная идея на доске) все равно остаются доступными для изменений.

 

Во взрослых языках программирования — в частности, в Haskell — иммутабельность означает именно то, что и подразумевается. Создадим список из любых объектов — и далее мы можем делать с этим списком и его содержимым всё что угодно, но его исходная версия останется нетронутой. Это действительно гарантируется языком, и мы можем вообще не волноваться об этом.

 

Но на практике оказывается, что иммутабельность имеет две трактовки — в зависимости от языка! Получается, что нам надо знать детали реализации языка, чтобы учитывать, что же на самом деле «неизменно».

 

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

 

Когда мы вызваем чистую (pure) функцию с иммутабельным аргументом, она всегда должна возвращать один и тот же результат.

 

В нашем примере это не так. Определим чистую функцию

def pw(T):

    return T[0].power*T[0].power

которая получает на вход иммутабельный кортеж.

Вызовем её с одним и тем же параметром — кортежом tpl в начале и конце, и получим разный результат!

Поделиться статьей ...Share on Facebook0Share on Google+0Tweet about this on TwitterShare on LinkedIn0Share on VKPrint this page

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *