me(3)")
3 ob <- 4.some applyLog Vector("Entered Some(4)")
4 } yield ^(oa,ob){_ * _} //> res0: Exercises.logger.Logger[scala.collection.immutable.Vector[String],Opti
5 //| on[Int]] = Logger(Vector(Entered Some(3), Entered Some(4)),Some(12))
一样可以使用。注意oa,ob是Option类型所以必须使用^(oa,ob){...}来结合它们。
我们再来看看Logger的典型应用:一个gcd(greatest common denominator)算法例子:
1 def gcd(x: Int, y: Int): Logger[Vector[String], Int] = { 2 if (y == 0 ) for { 3 _ <- x applyLog Vector("Finished at " + x) 4 } yield x 5 else
6 x applyLog Vector(x.shows + " mod " + y.shows + " = " + (x % y).shows) >>= {_ => gcd(y, x % y) } 7
8 } //> gcd: (x: Int, y: Int)Exercises.logger.Logger[Vector[String],Int]
9 gcd(18,6) //> res5: Exercises.logger.Logger[Vector[String],Int] = Logger(Vector(18 mod 6 10 //| = 0, Finished at 6),6)
11 gcd(8,3) //> res6: Exercises.logger.Logger[Vector[String],Int] = Logger(Vector(8 mod 3 = 12 //| 2, 3 mod 2 = 1, 2 mod 1 = 0, Finished at 1),1)
注意 >>= 符号的使用,显现了Logger的Monad实例特性。
实际上scalar提供了Writer数据结构,它是WriterT类型的一个特例:
1 type Writer[+W, +A] = WriterT[Id, W, A]
我们再看看WriterT:scalaz/WriterT.scala
final case class WriterT[F[_], W, A](run: F[(W, A)]) { self => ...
WriterT在运算值A之外增加了状态值W,形成一个对值(paired value)。这是一种典型的FP状态维护模式。不过WriterT的这个(W,A)是在运算模型F[]内的。这样可以实现更高层次的概括,为这种状态维护的运算增加多一层运算协议(F[])影响。我们看到Writer运算是WriterT运算模式的一个特例,它直接计算运算值,不需要F[]影响,所以Writer的F[]采用了Id,因为Id[A] = A。我们看看WriterT是如何通过flatMap来实现状态维护的:scalaz/WriterT.scala:
1 def flatMap[B](f: A => WriterT[F, W, B])(implicit F: Bind[F], s: Semigroup[W]): WriterT[F, W, B] =
2 flatMapF(f.andThen(_.run)) 3
4 def flatMapF[B](f: A => F[(W, B)])(implicit F: Bind[F], s: Semigroup[W]): WriterT[F, W, B] =
5 writerT(F.bind(run){wa =>
6 val z = f(wa._2) 7 F.map(z)(wb => (s.append(wa._1, wb._1), wb._2)) 8 })
在flatMapF函数里对(W,A)的W进行了Monoid append操作。
实际上Writer可以说是一种附加的数据结构,它在运算模型F[A]内增加了一个状态值W形成了F(W,A)这种形式。当我们为任何类型A提供注入方法来构建这个Writer结构后,任意类型的运算都可以使用Writer来实现在运算过程中增加附加作用如维护状态、logging等等。我们看看scalaz/Syntax/WriterOps.scala:
package scalaz package syntax final class WriterOps[A](self: A) { def set[W](w: W): Writer[W, A] = WriterT.writer(w -> self) def tell: Writer[A, Unit] = WriterT.tell(self) } trait ToWriterOps { implicit def ToWriterOps[A](a: A) = new WriterOps(a) }
存粹是方法注入。现在任何类型A都可以使用set和tell来构建Writer类型了:
1 3 set Vector("Entered Int 3") //> res2: scalaz.Writer[scala.collection.immutable.Vector[String],Int] = WriterT 2 //| ((Vector(Entered Int 3),3))
3 "hi" set Vector("say hi") //> res3: scalaz.Writer[scala.collection.immutable.Vector[String],String] = Writ 4 //| erT((Vector(say hi),hi))
5 List(1,2,3) set Vector("list 123") //> res4: scalaz.Writer[scala.collection.immutable.Vector[String],List[Int]] = W 6 //| riterT((Vector(list 123),List(1, 2, 3)))
7 3.some set List("some 3") //> res5: scalaz.Writer[List[String],Option[Int]] = WriterT((List(some 3),Some(3 8 //| )))
9 Vector("just say hi").tell //> res6: scalaz.Writer[scala.collection.immutable.Vector[String],Unit] = Writer 10 //| T((Vector(just say hi),()))
用Writer运算上面Logger的例子:
1 for { 2 a <- 3 set "Entered Int 3 "
3 b <- 4 set "Entered Int 4 "
4 c <- "Result:" set "Ente