Что такое чистая функция?

Чистая функция (pure function) — это функция, которая при одинаковых входных данных всегда возвращает одинаковый результат и не имеет побочных эффектов. Это фундаментальное понятие в функциональном программировании, и в языке Elixir оно занимает центральное место.

Основные свойства чистой функции

Детерминированность (предсказуемость)
При одном и том же входе результат всегда одинаковый.
Например:

def square(x), do: x \* x
square(5) # всегда 25
  1. Отсутствие побочных эффектов (side effects)
    Побочные эффекты — это любое взаимодействие с внешним миром:

    • Изменение глобальных переменных

    • Запись в файл или базу данных

    • Отправка HTTP-запроса

    • Логирование

    • Работа с IO (например, IO.puts)

  2. Чистая функция не делает ничего из вышеперечисленного — она не изменяет состояния вне себя и не зависит от него.

Примеры

Чистая функция:

def add(a, b), do: a + b
  • Нет побочных эффектов.

  • Результат зависит только от аргументов.

Нечистая функция:

def impure_add(a, b) do
IO.puts("Calculating...")
a + b
end
  • Есть побочный эффект: вывод в консоль.

  • Хотя результат тот же, функция больше не является чистой.

Почему чистые функции важны

  • Упрощают отладку и тестирование. Их поведение предсказуемо, не нужно моделировать состояние среды.

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

  • Позволяют легко строить композиции. Результат одной функции можно передавать в другую без опасений за глобальные эффекты.

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

Примеры из Elixir

\# Чистая функция
defmodule Math do
def multiply(a, b), do: a \* b
end
\# Нечистая функция
defmodule LoggerExample do
def add_and_log(a, b) do
IO.puts("Складываем #{a} и #{b}")
a + b
end
end

Работа с чистыми функциями в Elixir

Elixir поощряет чистые функции:

  • Данные неизменяемы (иммутабельны).

  • Нет доступа к глобальным переменным.

  • Весь ввод-вывод и побочные эффекты вынесены в отдельные модули/процессы.

Чистота и рекурсия

Чистые функции часто используются в рекурсивных вычислениях. Пример:

defmodule Sum do
def total(\[\]), do: 0
def total(\[head | tail\]), do: head + total(tail)
end

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

Влияние чистоты на проектирование

  • Код становится более модульным — каждый компонент можно проверить изолированно.

  • Чистые функции можно кешировать, поскольку они всегда дают одинаковый результат.

  • Их можно автоматически тестировать, не подготавливая окружение.

Где граница

Некоторые функции кажутся чистыми, но это не так, если они:

  • Зависят от системного времени (:calendar.local_time(), System.os_time()).

  • Используют случайные числа (:rand.uniform()).

  • Работают с внешними API.

Пример нечистой функции:

def get_time_and_greet(name) do
time = :calendar.local_time()
"Hello, #{name}. The time is #{inspect(time)}"
end

Здесь результат зависит от текущего времени.

Обход ограничений

В Elixir принято разделять чистую и нечистую логику. Например, IO делают отдельно:

defmodule Greeter do
def greeting(name), do: "Hello, #{name}"
end
defmodule Main do
def run do
name = "Elixir"
msg = Greeter.greeting(name)
IO.puts(msg)
end
end

Так Greeter.greeting/1 остаётся чистой, а побочный эффект (IO.puts) вынесен наружу.

Роль в функциональном программировании

В функциональных языках (включая Elixir):

  • Чистые функции — основной строительный блок.

  • Состояние моделируется через передачу аргументов.

  • Побочные эффекты — строго контролируемые участки кода.

Это приводит к прозрачному, надёжному и легко масштабируемому коду.