猫作家矢量为空
Cats Writer Vector is empty
我编写了这个简单的程序来尝试了解 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" 方法的所有字符串吗?
您的示例代码的问题是您没有使用 tell
方法的结果。
如果你看一下它的签名,你会看到:
final class WriterIdSyntax[A](val a: A) extends AnyVal {
def tell: Writer[A, Unit] = Writer(a, ())
}
很明显,tell
return 是一个 Writer[A, Unit]
结果,因为您没有给它赋值,所以它会立即被丢弃。
使用 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))
如您所见,结果中存储了 messages 和 int。
现在这有点难看,Scala 实际上提供了一种更好的方法来做到这一点:for-comprehensions。 For-comprehension 是一些语法糖,允许我们以这种方式编写相同的代码:
println(
for {
_ <- Vector("started the program").tell
i <- 15.pure[Logged2]
_ <- Vector("ended program").tell
} yield i
)
现在回到您的示例,我建议您将 compute1
和 compute2
的 return 类型更改为 Writer[Vector[String], Int]
,然后尝试使用我上面写的内容编译你的应用程序。
当您在 Vector
上调用 tell
时,每次创建 Writer[Vector[String], Unit]
。然而,你实际上从来没有对你的Writer
做任何事情,你只是丢弃它们。此外,您调用 pure
来创建最终的 Writer
,它只是创建一个带有空 Vector
的 Writer
。您必须将作者 组合成一个链条,传递您的价值和信息。
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
是 monadic bind 操作,这意味着它了解如何获取两个 monadic 值(在本例中为 Writer
)并将它们连接在一起得到一个新的。在本例中,它生成一个 Writer
包含日志的串联和右侧的值。
注意没有副作用。没有 Writer
可以记住您对 tell
的所有调用的全局状态。相反,您制作了许多 Writer
,然后将它们与 flatMap
连接在一起,最后得到一个大的。
我编写了这个简单的程序来尝试了解 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" 方法的所有字符串吗?
您的示例代码的问题是您没有使用 tell
方法的结果。
如果你看一下它的签名,你会看到:
final class WriterIdSyntax[A](val a: A) extends AnyVal {
def tell: Writer[A, Unit] = Writer(a, ())
}
很明显,tell
return 是一个 Writer[A, Unit]
结果,因为您没有给它赋值,所以它会立即被丢弃。
使用 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))
如您所见,结果中存储了 messages 和 int。
现在这有点难看,Scala 实际上提供了一种更好的方法来做到这一点:for-comprehensions。 For-comprehension 是一些语法糖,允许我们以这种方式编写相同的代码:
println(
for {
_ <- Vector("started the program").tell
i <- 15.pure[Logged2]
_ <- Vector("ended program").tell
} yield i
)
现在回到您的示例,我建议您将 compute1
和 compute2
的 return 类型更改为 Writer[Vector[String], Int]
,然后尝试使用我上面写的内容编译你的应用程序。
当您在 Vector
上调用 tell
时,每次创建 Writer[Vector[String], Unit]
。然而,你实际上从来没有对你的Writer
做任何事情,你只是丢弃它们。此外,您调用 pure
来创建最终的 Writer
,它只是创建一个带有空 Vector
的 Writer
。您必须将作者 组合成一个链条,传递您的价值和信息。
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
是 monadic bind 操作,这意味着它了解如何获取两个 monadic 值(在本例中为 Writer
)并将它们连接在一起得到一个新的。在本例中,它生成一个 Writer
包含日志的串联和右侧的值。
注意没有副作用。没有 Writer
可以记住您对 tell
的所有调用的全局状态。相反,您制作了许多 Writer
,然后将它们与 flatMap
连接在一起,最后得到一个大的。