layout | title | scala3 | partof | overview-name | type | description | language | num | previous-page | next-page |
---|---|---|---|---|---|---|---|---|---|---|
multipage-overview |
Чистые функции |
true |
scala3-book |
Scala 3 — Book |
section |
В этом разделе рассматривается использование чистых функций в функциональном программировании. |
ru |
44 |
fp-immutable-values |
fp-functions-are-values |
Еще одна концепция, которую 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 расширение][eta].
И наоборот, следующие функции “грязные” (impure), потому что они нарушают определение pure function:
println
— методы, взаимодействующие с консолью, файлами, базами данных, веб-сервисами и т.д., “грязные”currentTimeMillis
— все методы, связанные с датой и временем, “грязные”, потому что их вывод зависит от чего-то другого, кроме входных параметровsys.error
— методы генерации исключений “грязные”, потому что они не “просто возвращают результат”
“Грязные” функции часто делают одно из следующего:
- читают из скрытого состояния, т.е. обращаются к параметрам и данным, не переданным в функцию явным образом в качестве входных параметров
- запись в скрытое состояние
- изменяют заданные им параметры или изменяют скрытые переменные, например, поля в содержащем их классе
- выполняют какой-либо ввод-вывод с внешним миром
В общем, следует остерегаться функций с возвращаемым типом
Unit
. Поскольку эти функции ничего не возвращают, логически единственная причина, по которой они когда-либо вызываются, - это достижение какого-то побочного эффекта. Как следствие, часто использование этих функций является “грязным”.
Конечно, приложение не очень полезно, если оно не может читать или писать во внешний мир, поэтому рекомендуется следующее:
Напишите ядро вашего приложения, используя только “чистые” функции, а затем напишите “грязную” “оболочку” вокруг этого ядра для взаимодействия с внешним миром. Как кто-то однажды сказал, это все равно, что положить слой нечистой глазури на чистый торт.
Важно отметить, что есть способы сделать “нечистое” взаимодействие с внешним миром более “чистым”.
Например, можно услышать об использовании IO
монады для обработки ввода-вывода.
Эти темы выходят за рамки данного документа, поэтому для простоты можно думать,
что ФП приложения имеют ядро из “чистых” функций,
которые объединены с другими функциями для взаимодействия с внешним миром.
Примечание: в этом разделе для обозначения методов Scala часто используется общепринятый в отрасли термин “чистая функция”.
Для написания чистых функций на Scala, достаточно писать их, используя синтаксис методов Scala (хотя также можно использовать и синтаксис функций Scala). Например, вот чистая функция, которая удваивает заданное ей входное значение:
{% tabs fp-pure-function %}
{% tab 'Scala 2 и 3' %}
def double(i: Int): Int = i * 2
{% endtab %}
{% endtabs %}
Вот чистая функция, которая вычисляет сумму списка целых чисел с использованием рекурсии:
{% tabs fp-pure-recursive-function class=tabs-scala-version %}
{% tab 'Scala 2' %}
def sum(xs: List[Int]): Int = xs match {
case Nil => 0
case head :: tail => head + sum(tail)
}
{% endtab %}
{% tab 'Scala 3' %}
def sum(xs: List[Int]): Int = xs match
case Nil => 0
case head :: tail => head + sum(tail)
{% endtab %}
{% endtabs %}
Вышеописанные функции соответствуют определению “чистых”.
Первым ключевым моментом этого раздела является определение чистой функции:
Чистая функция — это функция, которая зависит только от своих объявленных входных данных и своей реализации для получения результата. Она только вычисляет свой результат, не завися от внешнего мира и не изменяя его.
Второй ключевой момент заключается в том, что каждое реальное приложение взаимодействует с внешним миром. Таким образом, упрощенный способ представления о функциональных программах состоит в том, что они состоят из ядра чистых функций, которые обернуты другими функциями, взаимодействующими с внешним миром.
[eta]: {% link _overviews/scala3-book/fun-eta-expansion.md %}