Что такое чистая функция?
Чистая функция (pure function) — это функция, которая при одинаковых входных данных всегда возвращает одинаковый результат и не имеет побочных эффектов. Это фундаментальное понятие в функциональном программировании, и в языке Elixir оно занимает центральное место.
Основные свойства чистой функции
Детерминированность (предсказуемость)
При одном и том же входе результат всегда одинаковый.
Например:
def square(x), do: x \* x
square(5) # всегда 25
-
Отсутствие побочных эффектов (side effects)
Побочные эффекты — это любое взаимодействие с внешним миром:-
Изменение глобальных переменных
-
Запись в файл или базу данных
-
Отправка HTTP-запроса
-
Логирование
-
Работа с IO (например, IO.puts)
-
-
Чистая функция не делает ничего из вышеперечисленного — она не изменяет состояния вне себя и не зависит от него.
Примеры
Чистая функция:
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):
-
Чистые функции — основной строительный блок.
-
Состояние моделируется через передачу аргументов.
-
Побочные эффекты — строго контролируемые участки кода.
Это приводит к прозрачному, надёжному и легко масштабируемому коду.