Включите исполнение JavaScript в браузере, чтобы запустить приложение.
27 мая 2025

Что такое ссылочная прозрачность в программировании?

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

Что такое ссылочная прозрачность

Ссылочная прозрачность — это свойство выражений в коде, при котором они могут быть заменены своим результатом выполнения, при этом результат выполнения всей программы не изменится (то есть поведение кода не меняется). Результат вычисления ссылочно-прозрачной функции всегда одинаковый при одинаковых входных данных. Такие функции всегда чистые, так как обладают детерминированностью (при конкретном наборе входных данных возвращают одинаковый результат) и не имеют побочных эффектов.

Эта концепция является фундаментальной в функциональном программировании, однако может применяться и в императивном — тогда код становится более понятным и предсказуемым.

Роль ссылочной прозрачности в функциональном программировании

Функциональное программирование — это парадигма, построенная на выполнении функций в их математическом понимании. В функциональном программировании почти все функции являются чистыми, поэтому ссылочная прозрачность — значимая концепция для этого подхода. Зачем она нужна?

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

Во-вторых, концепция позволяет увеличивать скорость выполнения программ. Существует несколько способов оптимизации:

  • мемоизация — техника кеширования результатов вызова функции с целью избежания повторных вычислений. В рамках мемоизации перед выполнением выражения проверяется, вызывалось ли оно ранее: если да — используется сохраненный результат, если нет — функция выполняется, а результат сохраняется в кеш. Так как ссылочно-прозрачные выражения обладают детерминированностью, мемоизация отлично применима к ним;
  • удаление общих подвыражений — техника поиска вычисляемых более одного раза выражений на рассматриваемом участке кода. Например, x = (a + b) × c + (a + b) × d можно заменить на temp = a + b, x = temp × c + temp × d. Это уменьшает общее количество операций и позволяет использовать память эффективнее. Результат одного и того же ссылочно-прозрачного выражения всегда одинаковый, поэтому его можно использовать повторно;
  • ленивые вычисления — стратегия, в рамках которой вычисления выражений откладываются до тех пор, пока не понадобится их результат. Это позволяет экономить ресурсы и избегать лишних вычислений. Результат вычисления ссылочно-прозрачных функций не меняется со временем, поэтому их выполнение можно откладывать;
  • распараллеливание — техника одновременного (параллельного) выполнения независимых операций для ускорения вычислений. Ссылочно-прозрачные функции не имеют побочных эффектов и не изменяют поведение программы, поэтому к ним легко применить распараллеливание.

В-третьих, такие программы легче тестировать. Выполнение кода зависит только от входных данных, а значит, для тестирования нужно только несколько наборов данных, включающих пограничные случаи.

В-четвертых, ссылочная прозрачность в программе делает ее надежной и предсказуемой. Появление ошибок маловероятно в связи с отсутствием зависимости от внешнего состояния, а добавление новых подпрограмм не способно сломать код.

Ссылочная прозрачность в императивном программировании

Соблюдение концепции ссылочной прозрачности в императивном программировании — задача более сложная, так как языки к ней не адаптированы. Императивные языки часто вынуждают работать с побочными эффектами, а данные в процессе выполнения программ могут изменяться.

Что можно сделать, чтобы поддерживать ссылочную прозрачность в коде:

  • не использовать глобальные переменные, когда это возможно. Они доступны в любом месте программы, а функции зависит от их текущего состояния, поэтому результат выполнения становится непредсказуемым;
  • сокращать изменение данных. Например, реализовывать функции, создающие новый массив вместо изменения исходного;
  • минимизировать побочные эффекты. Полностью исключить их возможно редко, так, например, ввод, вывод или запись данных в файл — необходимые компоненты многих программ.

Так, концепцию можно соблюдать и в императивном программировании, однако писать полностью ссылочно-прозрачный код получится не всегда, также важно делать это разумно, чтобы не нарушать функциональность программы.

Примеры функций со ссылочной прозрачностью

В программировании ссылочная прозрачность может применяться к программам в целом или к их отдельным частям. Если вызов функции можно заменить значением, которое она возвращает без изменения поведения программы, то ее можно назвать ссылочно-прозрачной. Как уже отмечалось, эта концепция применима в том числе к императивным языкам, поэтому рассмотрим примеры на Python и JavaScript:

  • Функции, выполняющие арифметические операции — это наиболее простой пример ссылочной прозрачности:
onst add = (a, b) => a + b;
const mult = (a, b) => a * b;




const result = add(4, mult(2, 6));
// Здесь mult(2, 6) может быть заменено на 12 — значение, которое она возвращает


const result = add(4, 12);
/* Выполнение add(4, 12) вернет 16, выражение также можно заменить на этот результат, не меняя поведения программы. Кроме того, его можно заменить на любое другое выражение, которое возвращает равное значение — приведем несколько примеров ниже
*\
const result = add(8, 8);
const result = add(6, 10);
const result = add(1, 15);
javascript

Так, функции возвращают одинаковые значения при одинаковых входных данных и не имеют побочных эффектов, а их вызов можно заменить на возвращаемый результат (или аналогичное выражение), поэтому являются ссылочно-прозрачными.

  • Конкатенация строк — еще один пример ссылочной прозрачности (в некоторых случаях):
// concatenate() обладает детерминированностью и не вызывает побочных эффектов, ее вызов можно заменить на значение «Hello, world»
def concatenate(a, b):
    return a + b


result = concatenate("Hello, ", "world!")


// При этом конкатенация перестает быть ссылочно-прозрачной, если зависит от глобальной переменной
separator = " "
def concatenate(a, b):
    return a + separator + b


// Ситуация аналогична с функциями, зависящими от недетерминированного фактора, например, текущего времени
import time


def concatenate(a, b):
    return a + b + str(int(time.time()))


// Также не ссылочно-прозрачна такая функция с выводом в консоль, так как она имеет побочный эффект
def concatenate(a, b):
    print(f"Concatenating {a} and {b}")
    return a + b
py

Третий пример ссылочной прозрачности — рекурсия:

def sum_up_to(n):
    if n == 0:
        return 0
    return n + sum_up_to(n - 1)
// Выражение sum_up_to(5) детерминировано, его можно заменить на возвращаемое значение (15), и оно не вызывает побочных эффектов
py

Также ссылочная прозрачность может наблюдаться при обработке коллекций:

const doubleValues = (arr) => arr.map(x => x * 2);


const numbers = [1, 2, 3];
const doubled = doubleValues(numbers);


/* Для одного и того же массива результат doubleValues() всегда будет одинаковым, побочных эффектов она не вызывает, так как map() создает новый массив и не меняет исходный. Вызов doubleValues() можно заменить на результат выполнения — это не изменит поведение программы
*\
javascript

Эта функция остается ссылочно-прозрачной, даже если массив меняется между вызовами, например, туда добавляются новые значения.

Дополнительно рассмотрим еще несколько примеров не ссылочно-прозрачных функций:

Сюда относятся любые выражения с побочными эффектами:

// Запись в файл
const fs = require('fs');


const readFileContent = (path) => fs.readFileSync(path, 'utf-8');


const content = readFileContent('example.txt');


# Ввод данных (Python)
def get_user_input():
    return input("Введите сообщение: ")


response = get_user_input()
javascript

Концепцию также нарушают функции, меняющие состояние объектов:

class Counter {
    constructor() {
        this.count = 0;
    }


    increment() {
        this.count+=1;
    }


    getCount() {
        return this.count;
    }
}
javascript

Преимущества использования ссылочной прозрачности

К плюсам соблюдения концепции можно отнести:

  • легкость чтения и понимания кода;
  • возможность применения техник оптимизации для увеличения скорости выполнения программ;
  • удобство рефакторинга, тестирования и отладки;
  • улучшение структуры кода, повышение модульности.

Ссылочная прозрачность vs побочные эффекты в функциональных языках

Побочные эффекты — это любые операции, при которых происходит взаимодействие с внешним окружением. Например, ввод и вывод данных, изменение глобальной переменной, запись информации в файл или БД.

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

  • неизменяемость данных — созданные значения нельзя изменить;
  • монады (в Haskell), изолирующие побочные эффекты от программы.