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

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

Два ключевых признака чистой функции

  1. Детерминированность (deterministic)
    Результат зависит только от входных параметров.
    Если вызвать функцию f(2, 3) — она всегда вернёт одно и то же значение.

  2. Отсутствие побочных эффектов (no side effects)
    Функция не изменяет внешнее состояние и не зависит от него.
    Запрещено:

    • Модифицировать глобальные переменные

    • Изменять аргументы по ссылке

    • Писать в файл, консоль, БД

    • Читать случайные данные или текущее время

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

def square(x: Int): Int = x \* x
  • Выход зависит только от x

  • Никаких побочных эффектов

  • Без доступа к внешнему миру

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

var counter = 0
def increment(x: Int): Int = {
counter += 1
x + counter
}
  • Используется и изменяется глобальное состояние

  • При одном и том же x, результат может быть разным

  • Функция не является детерминированной

Побочные эффекты — что это?

Побочный эффект (side effect) — это любое воздействие функции на внешний мир или зависимость от него.

Примеры:

  • Печать в консоль (println)

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

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

  • Генерация случайного числа (Random.nextInt)

  • Чтение системного времени (System.currentTimeMillis)

  • Изменение аргументов по ссылке

Если функция делает хотя бы одно из вышеуказанного — она нечистая.

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

1. Тестируемость

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

assert(square(5) == 25)

2. Параллелизм и многопоточность

Чистые функции не используют или не изменяют глобальное состояние, поэтому их можно безопасно выполнять параллельно.

3. Переиспользуемость

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

4. Кэширование (memoization)

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

Чистые функции в Scala

Scala как мультипарадигменный язык позволяет писать как чистые, так и нечистые функции. Однако при функциональном стиле кодирования акцент делается именно на чистые.

Примеры:

// Чистая
def add(a: Int, b: Int): Int = a + b
// Чистая
def capitalize(s: String): String = s.toUpperCase
// Не чистая (пишет в консоль)
def greet(name: String): Unit = println(s"Hello, $name!")

Сравнение: чистая vs нечистая

Функция Чистая Побочный эффект
def add(x: Int, y: Int) = x + y
--- --- ---
def log(x: String) = println(x) ✅ (печать)
--- --- ---
def now() = System.currentTimeMillis ✅ (время)
--- --- ---
def sqrt(x: Double) = Math.sqrt(x)
--- --- ---

Можно ли писать полезную программу только на чистых функциях?

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

Например:

def calculateTax(income: Double): Double = income \* 0.13
def main(): Unit = {
val income = scala.io.StdIn.readDouble()
val tax = calculateTax(income)
println(s"Tax: $tax")
}
  • calculateTax — чистая

  • main — нечистая (ввод/вывод)

Higher-order функции и чистота

Многие функции из List, Seq, Option, Either работают с чистыми функциями:

List(1, 2, 3).map(x => x + 1) // List(2, 3, 4)

Здесь map ожидает чистую функцию (Int) => Int.

Связь с иммутабельностью

Чистые функции хорошо работают с неизменяемыми структурами данных (immutable structures), потому что не полагаются на изменение состояния. Это делает код безопасным в конкурентной среде.

Каррирование и чистые функции

Каррирование помогает разбивать чистые функции на части:

def multiply(a: Int)(b: Int): Int = a \* b
val timesTwo = multiply(2) _
val result = timesTwo(5) // 10

timesTwo — частично применённая чистая функция.