Функциональный журнал
Описание
Writer
предназначен для результатов, к которым присоединено другое значение, действующее как своего рода журнал.
Одним из распространенных применений для Writer
является запись последовательностей шагов в многопоточных вычислениях,
где стандартные методы императивной регистрации могут привести к чередованию сообщений из разных контекстов.
При этом лог для вычислений Writer
привязан к результату,
поэтому можно запускать параллельные вычисления без смешивания логов.
final case class Writer[W, A](run: () => (W, A))
Функции общего назначения
map
Аналогично функциональному состоянию, map
позволяет при наличии базового журнала Writer[S, A]
и функции преобразования значения из типа A
в тип B
получать Writer[S, B]
-
функциональный журнал с вычислением значения типа B
.
object Writer:
extension [W, A](underlying: Writer[W, A])
def map[B](f: A => B): Writer[W, B] =
val (w, a) = underlying.run()
Writer[W, B](() => (w, f(a)))
Пример:
val initial = Writer[String, Int](() => ("Сообщение:", 1))
val writer = initial.map(_ + 1)
writer.run()
// (Сообщение:,2)
flatMap
flatMap
позволяет при наличии функции преобразования значения в новый Writer
получать этот Writer
:
object Writer:
extension [W, A](underlying: Writer[W, A])
def flatMap[B](f: A => Writer[W, B])(combine: (W, W) => W): Writer[W, B] =
Writer[W, B] { () =>
val (w1, a) = underlying.run()
val (w2, b) = f(a).run()
(combine(w1, w2), b)
}
Пример:
val initial = Writer[String, Int](() => ("Сообщение:", 1))
val f: Int => Writer[String, Int] = number =>
if number <= 0 then Writer(() => ("Конфеты кончились", number))
else Writer(() => ("Конфеты ещё есть", number))
val writer = initial.flatMap(f)(_ + _)
writer.run()
// (Сообщение:Конфеты ещё есть,1)
unit
unit
оборачивает любое значение в очень простой Writer
с заданным постоянным "сообщением":
object Writer:
def unit[W, A](a: => A)(empty: W): Writer[W, A] =
Writer[W, A](() => (empty, a))
Пример:
unit[String, Int](42)("").run()
// ("", 42)
Реализация в библиотеках
Реализация в Cats
import cats.data.Writer
val a = Writer(Vector(
"It was the best of times",
"it was the worst of times"
), 1859)
val (log, result) = a.run
// val log: Vector[String] = Vector(It was the best of times, it was the worst of times)
// val result: Int = 1859
Ссылки:
- Category Theory for Programmers - Bartosz Milewski:
- Cats
- Herding Cats
- Learning Scalaz
- Scala with Cats