Еще одна концепция, которую Scala предлагает для помощи в написании функционального кода, — это возможность писать чистые функции. Чистая функция (pure function) может быть определена следующим образом:
- функция
f
является чистой, если при одних и тех же входных данныхx
она всегда возвращает один и тот же результатf(x)
- результат функции зависит только от входных данных и её реализации
- чистые функции только вычисляют результат, ничего не меняя за пределами этих функций
Из этого следует:
- чистая функция не изменяет свои входные параметры
- она не мутирует какое-либо скрытое состояние
- у неё нет “черных ходов”: он не читает данные из внешнего мира (включая консоль, веб-сервисы, базы данных, файлы и т.д.) и не записывает данные вовне
В результате этого определения каждый раз, когда вызывается чистая функция с одним и тем же входным значением (значениями),
всегда будет выдаваться один и тот же результат.
Например, можно вызывать функцию double
бесконечное число раз с входным значением 2
, и всегда получать результат 4
.
Примеры чистых функций
Учитывая это определение, методы в пакете scala.math._
являются чистыми функциями:
abs
ceil
max
Эти методы String
также являются чистыми функциями:
isEmpty
length
substring
Большинство методов в классах коллекций Scala также работают как чистые функции,
включая drop
, filter
, map
и многие другие.
В Scala функции и методы почти полностью взаимозаменяемы, поэтому, хотя здесь используется общепринятый отраслевой термин “чистая функция”, этот термин можно использовать как для описания функций, так и методов. Как методы могут использоваться подобно функциям описано в главе Eta расширение.
Примеры “грязных” функций
И наоборот, следующие функции “грязные” (impure), потому что они нарушают определение pure function:
println
— методы, взаимодействующие с консолью, файлами, базами данных, веб-сервисами и т.д., “грязные”currentTimeMillis
— все методы, связанные с датой и временем, “грязные”, потому что их вывод зависит от чего-то другого, кроме входных параметровsys.error
— методы генерации исключений “грязные”, потому что они не “просто возвращают результат”
“Грязные” функции часто делают одно из следующего:
- читают из скрытого состояния, т.е. обращаются к параметрам и данным, не переданным в функцию явным образом в качестве входных параметров
- запись в скрытое состояние
- изменяют заданные им параметры или изменяют скрытые переменные, например, поля в содержащем их классе
- выполняют какой-либо ввод-вывод с внешним миром
В общем, следует остерегаться функций с возвращаемым типом
Unit
. Поскольку эти функции ничего не возвращают, логически единственная причина, по которой они когда-либо вызываются, - это достижение какого-то побочного эффекта. Как следствие, часто использование этих функций является “грязным”.
Но грязные функции все же необходимы …
Конечно, приложение не очень полезно, если оно не может читать или писать во внешний мир, поэтому рекомендуется следующее:
Напишите ядро вашего приложения, используя только “чистые” функции, а затем напишите “грязную” “оболочку” вокруг этого ядра для взаимодействия с внешним миром. Как кто-то однажды сказал, это все равно, что положить слой нечистой глазури на чистый торт.
Важно отметить, что есть способы сделать “нечистое” взаимодействие с внешним миром более “чистым”.
Например, можно услышать об использовании IO
монады для обработки ввода-вывода.
Эти темы выходят за рамки данного документа, поэтому для простоты можно думать,
что ФП приложения имеют ядро из “чистых” функций,
которые объединены с другими функциями для взаимодействия с внешним миром.
Написание “чистых” функций
Примечание: в этом разделе для обозначения методов Scala часто используется общепринятый в отрасли термин “чистая функция”.
Для написания чистых функций на Scala, достаточно писать их, используя синтаксис методов Scala (хотя также можно использовать и синтаксис функций Scala). Например, вот чистая функция, которая удваивает заданное ей входное значение:
def double(i: Int): Int = i * 2
Вот чистая функция, которая вычисляет сумму списка целых чисел с использованием рекурсии:
def sum(xs: List[Int]): Int = xs match {
case Nil => 0
case head :: tail => head + sum(tail)
}
def sum(xs: List[Int]): Int = xs match
case Nil => 0
case head :: tail => head + sum(tail)
Вышеописанные функции соответствуют определению “чистых”.
Ключевые моменты
Первым ключевым моментом этого раздела является определение чистой функции:
Чистая функция — это функция, которая зависит только от своих объявленных входных данных и своей реализации для получения результата. Она только вычисляет свой результат, не завися от внешнего мира и не изменяя его.
Второй ключевой момент заключается в том, что каждое реальное приложение взаимодействует с внешним миром. Таким образом, упрощенный способ представления о функциональных программах состоит в том, что они состоят из ядра чистых функций, которые обернуты другими функциями, взаимодействующими с внешним миром.
Contributors to this page:
Contents
- Введение
- Возможности Scala
- Почему Scala 3?
- Почувствуй Scala
- Пример 'Hello, World!'
- REPL
- Переменные и типы данных
- Структуры управления
- Моделирование данных
- Методы
- Функции первого класса
- Одноэлементные объекты
- Коллекции
- Контекстные абстракции
- Верхнеуровневые определения
- Обзор
- Первый взгляд на типы
- Интерполяция строк
- Структуры управления
- Моделирование предметной области
- Инструменты
- Моделирование ООП
- Моделирование ФП
- Методы
- Особенности методов
- Main методы в Scala 3
- Обзор
- Функции
- Анонимные функции
- Параметры функции
- Eta расширение
- Функции высшего порядка
- Собственный map
- Создание метода, возвращающего функцию
- Обзор
- Пакеты и импорт
- Коллекции в Scala
- Типы коллекций
- Методы в коллекциях
- Обзор
- Функциональное программирование
- Что такое функциональное программирование?
- Неизменяемые значения
- Чистые функции
- Функции — это значения
- Функциональная обработка ошибок
- Обзор
- Типы и система типов
- Определение типов
- Параметризованные типы
- Пересечение типов
- Объединение типов
- Алгебраические типы данных
- Вариантность
- Непрозрачные типы
- Структурные типы
- Зависимые типы функций
- Другие типы
- Контекстные абстракции
- Методы расширения
- Параметры контекста
- Контекстные границы
- Given импорты
- Классы типов
- Многостороннее равенство
- Неявное преобразование типов
- Обзор
- Параллелизм
- Scala утилиты
- Сборка и тестирование проектов Scala с помощью Sbt
- Рабочие листы
- Взаимодействие с Java