Подтвердить что ты не робот

Как вы используете scalaz.WriterT для входа в выражение for?

Как вы используете scalaz.WriterT для регистрации?

4b9b3361

Ответ 1

О монадных трансформаторах

Это очень короткое введение. Вы можете найти более подробную информацию о haskellwiki или этом большом слайде на @jrwest.

Монады не сочиняют, что означает, что если у вас есть монада A[_] и монада B[_], тогда A[B[_]] невозможно получить автоматически. Однако в большинстве случаев это может быть достигнуто за счет наличия так называемого монадного трансформатора для данной монады.

Если у нас есть monad transformer BT для monad B, то мы можем составить новую монаду A[B[_]] для любой монады A. Правильно, используя BT, мы можем поместить B внутрь A.

Использование трансформатора Монады в скалясе

Следующее предполагает скаляз 7, поскольку, честно говоря, я не использовал монадные трансформаторы со сказазом 6.

Монадный трансформатор MT принимает два типа параметров, первый - это оболочка (внешняя), вторая - фактический тип данных в нижней части стека монады. Примечание. Может потребоваться больше параметров типа, но они не связаны с трансформаторностью, а скорее специфичны для данной монады (например, для журнала Writer или типа ошибки Validation).

Итак, если у нас есть List[Option[A]], который мы хотели бы рассматривать как единственную составленную монаду, тогда нам нужно OptionT[List, A]. Если мы имеем Option[List[A]], нам нужно ListT[Option, A].

Как туда добраться? Если у нас есть значение без трансформатора, мы можем просто обернуть его с помощью MT.apply, чтобы получить значение внутри трансформатора. Чтобы вернуться из преобразованной формы в нормальную, мы обычно вызываем .run на преобразованное значение.

Итак, val a: OptionT[List, Int] = OptionT[List, Int](List(some(1)) и val b: List[Option[Int]] = a.run - это те же данные, просто представление отличается.

Было высказано Тони Моррисом, что лучше всего перейти в преобразованную версию как можно раньше и использовать это как можно дольше.

Примечание. Составление нескольких монадов с использованием трансформаторов дает стек трансформатора с типами, противоположным порядку, как обычный тип данных. Таким образом, нормальный List[Option[Validation[E, A]]] будет выглядеть примерно как type ListOptionValidation[+E, +A] = ValidationT[({type l[+a] = OptionT[List, a]})#l, E, A]

Обновление: по сравнению с scalaz 7.0.0-M2, Validation (правильно) не является монадом, поэтому ValidationT не существует. Вместо этого используйте EitherT.

Использование WriterT для ведения журнала

В соответствии с вашими потребностями вы можете использовать WriterT без какой-либо конкретной внешней монады (в этом случае в фоновом режиме она будет использовать монаду Id, которая ничего не делает), или может помещать журнал внутри monad, или поместите монаду в журнал.

Первый случай, простая запись

import scalaz.{Writer}
import scalaz.std.list.listMonoid
import scalaz._

def calc1 = Writer(List("doing calc"), 11)
def calc2 = Writer(List("doing other"), 22)

val r = for {
  a <- calc1
  b <- calc2
} yield {
  a + b
}

r.run should be_== (List("doing calc", "doing other"), 33)

Мы импортируем экземпляр listMonoid, так как он также предоставляет экземпляр Semigroup[List]. Это необходимо, так как WriterT нужен тип журнала для полугруппы, чтобы иметь возможность комбинировать значения журнала.

Второй случай, вход в монаду

Здесь мы выбрали монаду Option для простоты.

import scalaz.{Writer, WriterT}
import scalaz.std.list.listMonoid
import scalaz.std.option.optionInstance
import scalaz.syntax.pointed._

def calc1 = WriterT((List("doing calc") -> 11).point[Option])
def calc2 = WriterT((List("doing other") -> 22).point[Option])

val r = for {
  a <- calc1
  b <- calc2
} yield {
  a + b
}

r.run should be_== (Some(List("doing calc", "doing other"), 33))

При таком подходе, поскольку ведение журнала находится внутри монады Option, если какая-либо из связанных опций None, мы просто получим результат None без каких-либо журналов.

Примечание: x.point[Option] то же самое действует как Some(x), но может помочь лучше обобщить код. На данный момент это не смертоносное дело.

Третий вариант, запись вне монады

import scalaz.{Writer, OptionT}
import scalaz.std.list.listMonoid
import scalaz.std.option.optionInstance
import scalaz.syntax.pointed._

type Logger[+A] = WriterT[scalaz.Id.Id, List[String], A]

def calc1 = OptionT[Logger, Int](Writer(List("doing calc"), Some(11): Option[Int]))
def calc2 = OptionT[Logger, Int](Writer(List("doing other"), None: Option[Int]))

val r = for {
  a <- calc1
  b <- calc2
} yield {
  a + b
}

r.run.run should be_== (List("doing calc", "doing other") -> None)

Здесь мы используем OptionT для размещения монады Option внутри Writer. Одним из расчетов является None, чтобы показать, что даже в этом случае сохраняются журналы.

Заключительные замечания

В этих примерах List[String] использовался как тип журнала. Однако использование String вряд ли когда-либо является лучшим способом, только некоторые конвенции принудительно на нас, запустив фреймворки. Было бы лучше определить пользовательский лог файл ADT, например, и, если необходимо, вывести его в строку как можно позже. Таким образом, вы можете сериализовать журнал ADT и легко проанализировать его позже программным способом (вместо синтаксического анализа строк).

WriterT имеет множество полезных методов для работы, чтобы облегчить ведение журнала, проверьте источник. Например, с учетом w: WriterT[...] вы можете добавить новую запись в журнал с помощью w :++> List("other event") или даже зарегистрироваться с использованием текущего значения с помощью w :++>> ((v) => List("the result is " + v)) и т.д.

В примерах много явных и длинных кодов (типов, вызовов). Как всегда, это для ясности, рефакторинг их в вашем коде путем извлечения общих типов и операций.

Ответ 2

type OptionLogger[A] = WriterT[Option, NonEmptyList[String], A]

      val two: OptionLogger[Int] = WriterT.put(2.some)("The number two".pure[NonEmptyList])
      val hundred: OptionLogger[Int] = WriterT.put(100.some)("One hundred".pure[NonEmptyList])

      val twoHundred = for {
        a <- two
        b <- hundred
      } yield a * b

      twoHundred.value must be equalTo(200.some)


      val log = twoHundred.written map { _.list } getOrElse List() mkString(" ")
      log must be equalTo("The number two One hundred")