如何使用scalaz.WriterT登录for表达式?

如何使用scalaz.WriterT登录for表达式?,scala,logging,scalaz,Scala,Logging,Scalaz,如何使用scalaz.WriterT进行日志记录?关于monad transformers 这是一个非常简短的介绍。您可能会找到关于或此的更多信息 单子不合成,这意味着如果你有一个单子a[\u]和一个单子B[\u],那么a[B[\u]]就不能自动派生。然而,在大多数情况下,这可以通过为给定的monad使用所谓的monad转换器来实现 如果我们有monad transformerBT用于monadB,那么我们可以为任何monada组合一个新的monada[B[\u]]。没错,通过使用BT,我们可以

如何使用scalaz.WriterT进行日志记录?

关于monad transformers

这是一个非常简短的介绍。您可能会找到关于或此的更多信息

单子不合成,这意味着如果你有一个单子
a[\u]
和一个单子
B[\u]
,那么
a[B[\u]]
就不能自动派生。然而,在大多数情况下,这可以通过为给定的monad使用所谓的monad转换器来实现

如果我们有monad transformer
BT
用于monad
B
,那么我们可以为任何monad
a
组合一个新的monad
a[B[\u]]
。没错,通过使用
BT
,我们可以把
B
放在
A
里面

scalaz中的Monad变压器使用情况

下面假设Scalaz7,因为坦率地说,我没有在Scalaz6中使用monad变压器

monad transformer
MT
接受两个类型参数,第一个是包装器(外部)monad,第二个是monad堆栈底部的实际数据类型。注意:可能需要更多的类型参数,但这些参数与转换器无关,而是特定于给定的monad(如
写入程序的记录类型,或
验证的错误类型)

因此,如果我们有一个
List[Option[a]]
,我们想把它当作一个单一的合成单子,那么我们需要
Option[List,a]
。如果我们有
选项[List[A]]
,我们需要
ListT[Option,A]

怎么去那里?如果我们有非变压器值,我们通常可以用
MT将其包装。apply
获取变压器内部的值。为了从转换后的表单恢复到正常状态,我们通常对转换后的值调用
。run

因此
val a:OptionT[List,Int]=OptionT[List,Int](List(一些(1))
val b:List[Option[Int]=a.run
是相同的数据,只是表示方式不同

托尼·莫里斯(Tony Morris)建议,最好尽早进入转换版本,并尽可能长时间使用

注意:使用transformers组合多个Monad会产生一个类型与正常数据类型顺序相反的transformer堆栈。因此,正常的
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进行日志记录

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)
根据您的需要,您可以使用
WriterT
,而不使用任何特定的外部monad(在本例中,在后台它将使用
Id
monad,它不做任何事情),或者可以将日志放在monad中,或者将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)
使用这种方法,因为日志记录在
选项
monad中,如果任何绑定选项是
None
,我们只会得到一个
None
结果,而不需要任何日志

注意:
x.point[Option]
Some(x)
在效果上相同,但可能有助于更好地概括代码。并非致命性,只是暂时这样做

第三个选项,在monad之外登录

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)
导入scalaz.{Writer,OptionT}
导入scalaz.std.list.listMonoid
导入scalaz.std.option.optionInstance
导入scalaz.syntax.pointed_
类型记录器[+A]=WriterT[scalaz.Id.Id,List[String],A]
def calc1=OptionT[Logger,Int](编写器(列表(“正在计算”),一些(11):选项[Int]))
def calc2=OptionT[Logger,Int](编写器(列表(“执行其他”),无:选项[Int]))
val r=用于{
a
type OptionLogger[a]=WriterT[Option,NonEmptyList[String],a]
valtwo:OptionLogger[Int]=WriterT.put(2.some)(“数字2.pure[NonEmptyList])
val一百:OptionLogger[Int]=WriterT.put(100.some)(“一百”.pure[NonEmptyList])
瓦尔两百={

注意:scalaz seven head即将推出一个
MonadWriter
typeclass。值得关注。我记得我把它看作是要点(由@puffnfresh发布)
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")