4

我编写了这个简单的程序,试图了解 Cats Writer 的工作原理

import cats.data.Writer
import cats.syntax.applicative._
import cats.syntax.writer._
import cats.instances.vector._

object WriterTest extends App {
   type Logged2[A] = Writer[Vector[String], A]
   Vector("started the program").tell
   val output1 = calculate1(10)
   val foo = new Foo()
   val output2 = foo.calculate2(20)
   val (log, sum) = (output1 + output2).pure[Logged2].run
   println(log)
   println(sum)

   def calculate1(x : Int) : Int = {
      Vector("came inside calculate1").tell
      val output = 10 + x
      Vector(s"Calculated value ${output}").tell
      output
   }
}

class Foo {
   def calculate2(x: Int) : Int = {
      Vector("came inside calculate 2").tell
      val output = 10 + x
      Vector(s"calculated ${output}").tell
      output
   }
}

该程序有效,输出为

> run-main WriterTest
[info] Compiling 1 Scala source to /Users/Cats/target/scala-2.11/classes...
[info] Running WriterTest 
Vector()
50
[success] Total time: 1 s, completed Jan 21, 2017 8:14:19 AM

但是为什么向量是空的?它不应该包含我使用“tell”方法的所有字符串吗?

4

2 回答 2

4

当你调用tellVector的 s 时,每次你创建一个Writer[Vector[String], Unit]. 但是,您实际上从未对您的 s任何事情Writer,您只是将它们丢弃。此外,您调用pure创建您的 final Writer,它只是创建一个Writer空的 a Vector。您必须作家组合在一起,形成一条承载您的价值和信息的链条。

type Logged[A] = Writer[Vector[String], A]

val (log, sum) = (for {
  _ <- Vector("started the program").tell
  output1 <- calculate1(10)
  foo = new Foo()
  output2 <- foo.calculate2(20)
} yield output1 + output2).run

def calculate1(x: Int): Logged[Int] = for {
  _ <- Vector("came inside calculate1").tell
  output = 10 + x
  _ <- Vector(s"Calculated value ${output}").tell
} yield output

class Foo {
  def calculate2(x: Int): Logged[Int] = for {
    _ <- Vector("came inside calculate2").tell
    output = 10 + x
    _ <- Vector(s"calculated ${output}").tell
  } yield output
}

注意for符号的使用。的定义calculate1是真的

def calculate1(x: Int): Logged[Int] = Vector("came inside calculate1").tell.flatMap { _ =>
  val output = 10 + x
  Vector(s"calculated ${output}").tell.map { _ => output }
}

flatMap是一元绑定操作,这意味着它了解如何获取两个一元值(在这种情况下Writer)并将它们连接在一起以获得一个新值。在这种情况下,它Writer包含日志的串联和右侧的值。

注意没有副作用。没有全局状态Writer可以记住您对tell. 相反,您可以制作许多Writers 并将它们连接在一起,flatMap最后得到一个大的。

于 2017-01-21T15:21:39.993 回答
1

您的示例代码的问题是您没有使用该tell方法的结果。

如果你看一下它的签名,你会看到:

final class WriterIdSyntax[A](val a: A) extends AnyVal {

   def tell: Writer[A, Unit] = Writer(a, ())

}

很明显,tell返回的Writer[A, Unit]结果会立即被丢弃,因为您没有将其分配给值。

使用 a Writer(以及 Scala 中的任何 monad)的正确方法是通过它的flatMap方法。它看起来类似于:

println(
  Vector("started the program").tell.flatMap { _ =>
    15.pure[Logged2].flatMap { i =>
      Writer(Vector("ended program"), i)
    }
  }
)

上面的代码在执行时会给你这个:

WriterT((Vector(started the program, ended program),15))

如您所见,消息和 int 都存储在结果中。

现在这有点难看,Scala 实际上提供了一种更好的方法来做到这一点:for-comprehensions。For-comprehension 是一种语法糖,它允许我们以这种方式编写相同的代码:

println(
  for {
    _ <- Vector("started the program").tell
    i <- 15.pure[Logged2]
    _ <- Vector("ended program").tell
  } yield i
)

现在回到您的示例,我建议您更改 and 的返回类型,compute1然后compute2尝试Writer[Vector[String], Int]使用我上面写的内容编译您的应用程序。

于 2017-01-21T15:19:12.657 回答