В функциональном программировании (FP) нам не рекомендуется изменять переменные состояния, как в объектно-ориентированном программировании (ООП), но это не означает, что состояния запрещены.

Одним из распространенных методов является использование эффектов в качестве значений: вместо неотслеживаемых переменных состояния мы кодируем состояние в возвращаемое значение.

Как это работает

Идея проста: взять начальное состояние, вернуть значение, рассчитанное на основе этого состояния, и предоставить обновленное состояние.

Переведите эту концепцию на Scala:

type State[S, A] = S => (A, S)

Здесь S — тип состояния, а A — тип значения, которое мы вычисляем. Поскольку мы не можем изменить состояние, каждый раз мы возвращаем новое состояние вместе со значением.

Пример

Допустим, мы хотим проверить наше расписание. У нас есть внешняя функция, checkScheduleкоторая сообщает нам, что в нашем календаре в данное время дня.

ООП
В мире ООП мы бы обернули все в класс с некоторыми закрытыми переменными, чтобы отслеживать время.

type Task = String
class OOPTodo(var time: Int):
 def nextTask: Task = 
 val (task, duration): (Task, Int) = checkSchedule(time) 
 time = (time+duration) % 24
 task

Мы начинаем день в 9 утра и продолжаем просить о следующей задаче, как показано на рисунке ниже, и все это делается с одним и тем же объектом.

Хотя это достигает того, чего мы хотим, этот код может показаться немного неуклюжим: мы можем двигаться только вперед, без возможности вернуться назад.

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

FP
Теперь давайте решим ту же задачу функциональным способом.

case class FPTodo(time: Int):
 def nextTask: (Task, FPTodo) = 
 val (task, duration): (Task, Int) = checkSchedule(time)
 val newTodo = FPTodo((time+duration)%24)
 (task, newTodo)

Каждый раз, когда мы запрашиваем следующую задачу, мы получаем новый объект списка дел для следующего шага, как показано оранжевыми стрелками.

При таком дизайне запрос одного и того же объекта всегда будет давать один и тот же результат.

Заключение

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

Это особенно полезно во время тестирования и отладки. Пока мы знаем состояние на данном шаге, мы можем надежно воспроизвести любой сценарий.