Referential Transparency (Ссылочная прозрачность)
Reference прозрачен, если его можно безопасно заменить на вычисленное значение или на ссылку на иммутабельный объект — без изменения поведения программы. Чистые функции автоматически дают RT. Эффект: безопасная мемоизация, оптимизации компилятора, нет race conditions.
Определение
Если можно заменить какое-то подвыражение или вызов функции, доступ к свойству, с приведением типов или без — то эта абстракция, этот reference, прозрачен.
Замена не изменяет поведение программы — значит, абстракция прозрачна для ссылок.
Чистые функции дают RT автоматически
Часто используется этот термин для того, чтобы сказать: когда используем чистые функции, код автоматически становится ссылочно-прозрачным.
const sum = (a, b) => a + b;
const result = sum(2, 3) * 10;
// эквивалентно
const result = 5 * 10;
// эквивалентно
const result = 50;
Все три формы взаимозаменяемы — это и есть RT.
Условия в JS
Чтобы выражение было RT:
- Нет async/await
- Нет коллбеков и I/O
- Нет обращений к внешнему mutable state
- Нет SharedArrayBuffer / других тредов
Компилятор (или человек) может тогда:
- Вынести инвариант из цикла
- Скомпактить общие подвыражения (CSE)
- Закешировать результат
- Распараллелить вычисления
Иммутабельные контейнеры
Если ссылки на контейнерные типы распространились по приложению, они всегда могут быть заменены на конкретное вычисленное значение, или это ссылка на другой иммутабельный объект.
Полилиния из иммутабельных точек:
const polyline = Object.freeze([
Object.freeze({ x: 0, y: 0 }),
Object.freeze({ x: 10, y: 5 }),
]);
// эту ссылку можно передавать куда угодно — никто не сломает данные
RT в ООП
Можно применять этот принцип и в ООП, если объекты только после создания (после выхода из конструктора/фабрики) потом не меняют состояние.
class Point {
#x; #y;
constructor(x, y) { this.#x = x; this.#y = y; }
// НЕ this.#x += dx, а возврат нового инстанса
move(dx, dy) { return new Point(this.#x + dx, this.#y + dy); }
}
Методы не меняют состояние, а отображают в следующий экземпляр. Старая ссылка гарантированно не изменится.
RT исключает race conditions
Использование RT никогда не приведёт к race conditions и порче данных. Никто не исправит данные в середине, никто не прочтёт неконсистентные данные.
Главное практическое преимущество — безопасное распараллеливание.
Защита иммутабельности в JS
| Способ | Гарантия |
|---|---|
| Сила воли + код-ревью | Слабая |
Object.freeze |
Поверхностная |
| Глубокий freeze рекурсивно | Сильная, но дорогая |
Замыкание + приватные поля # |
Сильная |
| Структура из Records (см. отдельную заметку) | Сильная |
Мемоизация и RT
Мемоизация не помешает RT. Каждый раз при обчислении можно вычислить с кэшем или без — если функции чистые и состояние иммутабельно.
Опасно: неидемпотентные операции (Date.now(), Math.random, обращение к внешнему API). Их кешировать НЕЛЬЗЯ — это сломает контракт.
Прячущийся state ломает RT
Может быть скрытый стейт. Мы можем кешировать данные, и в этом кеше они могут меняться. Обращение к методу пересчитается через кеш не так, как мы задумывали.
Кеш с TTL/eviction может вернуть разный результат на одинаковом входе → не RT.
Allowed side effects
Если подставляем геттер, и этот геттер что-то логирует — система будет в другом состоянии при многократных обращениях. Но бизнес-процесс получает одно и то же значение. Логирование не влияет на обчисление следующего состояния.
Логирование, метрики, телеметрия — терпимые side effects, не ломают RT с точки зрения бизнес-логики.
Из чего состоят RT-выражения
- Иммутабельные структуры данных
- Иммутабельные объекты, инстансы классов
- Чистые функции
- Константы и литералы
Связанные темы
Ресурсы
- Лекция RU: m03gMxXpIao
- Лекция UA: AhOtjiZ2svI