В функциональном программировании (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)
Каждый раз, когда мы запрашиваем следующую задачу, мы получаем новый объект списка дел для следующего шага, как показано оранжевыми стрелками.
При таком дизайне запрос одного и того же объекта всегда будет давать один и тот же результат.
Заключение
Отображение состояния на каждом шаге вычислений повышает прозрачность и воспроизводимость. Меньшее количество зависимостей от общих переменных упрощает понимание и поддержку кода.
Это особенно полезно во время тестирования и отладки. Пока мы знаем состояние на данном шаге, мы можем надежно воспроизвести любой сценарий.