Функциональный журнал

Описание

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

Ссылки: