上一篇只要是了解scalaz中的Monad,这一篇看几个例子。

Writer? I hardly knew her!

Whereas the Maybe monad is for values with an added context of failure, and the list monad is for nondeterministic values, Writer monad is for values that have another value attached that acts as a sort of log value.

让我们尝试实现applyLog函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import scalaz._
import Scalaz._
def isBigGang(x: Int): (Boolean, String) =
(x > 9, "Compared gang size to 9")
implicit class PairOps[A](pair: (A, String)) {
def applyLog[B](f: A => (B, String)): (B, String) = {
val (x, log) = pair
val (y, newlog) = f(x)
(y, log ++ newlog)
}
}
(3, "Samallish gang.") applyLog isBigGang

这里通过隐式类型转换,注入了applyLog,这里我们发现,并不用要求pair的第二个元素是String,只要能加就好,所以我们可以改成Monoid

1
2
3
4
5
6
7
implicit class PairOps[A, B: Monoid](pair: (A, B)) {
def applyLog[C](f: A => (C, B)): (C, B) = {
val (x, log) = pair
val (y, newlog) = f(x)
(y, log |+| newlog)
}
}

Writer

To attach a monoid to a value, we just need to put them together in a tuple. The Writer w a type is just a newtype wrapper for this.

scalaz中,可以用这种方法构造一个Writer

1
type Writer[+W, +A] = WriterT[Id, W, A]

WriterT

下面是个简化版的WriterT

1
2
3
4
5
6
7
8
sealed trait WriterT[F[+_], +W, +A] { self =>
val run: F[(W, A)]
def written(implicit F: Functor[F]): F[W] =
F.map(run)(_._1)
def value(implicit F: Functor[F]): F[A] =
F.map(run)(_._2)
}

通常,我们可以这么使用

1
3.set("Smallish gang.")

set的操作是通过WriterOps注入的,

1
2
3
4
5
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)
}

通过注入这两个操作,我们可以简单的从普通类型构造出Writer

1
2
3
4
3.set("something")
// res1: scalaz.Writer[String,Int] = WriterT((something,3))
"hehehehe".tell
// res2: scalaz.Writer[String,Unit] = WriterT((hehehehe,()))

Using for syntax with Writer

Now that we have a Monad instance, we’re free to use do notation for Writer values.

我们尝试把Writer用到我们的程序里面,

1
2
3
4
5
6
7
8
def gcd(a: Int, b: Int): Writer[List[String], Int] =
if (b == 0) for {
_ <- List("Finished with " + a.shows).tell
} yield a
else
List(a.shows + " mod " + b.shows + " = " + (a % b).shows).tell >>= { _ =>
gcd(b, a % b)
}

不过,同样是Monoid,不同类型的|+|的效率不同,这里为了提高效率,换成Vector更好。

Reader

我们已经知道(->) r其实是functor

1
2
3
4
val f = (_: Int) + 3
val g = (_: Int) * 5
(g map f)(3)
// res0: Int = 18

然后也知道了,函数其实也是Applicative,这使我们可以像已经拥有了结果一样,直接操作他们。

1
2
3
4
5
val f = ({(_: Int) * 2}
|@| {(_: Int) + 10}) {_ + _}
f(13)
// res0: Int = 49

函数不光是FunctorApplicative,他还是Monad

1
2
3
4
5
6
7
val f = for {
a <- (_: Int) * 2
b <- (_: Int) + 10
} yield a + b
f(12) // res0: Int = 46

这里就很明显体现了,其实我们明明没有得到ab的值,却可以直接使用它们,然后获得新的函数。这里的function monad又被称为reader monad

不像OptionListWriterReader都没在标准库提供,是完全由scalaz实现的。




X