通过前面的几篇讨论我们了解到F[T]就是FP中运算的表达形式(representation of computation)。在这里F[]不仅仅是一种高阶类型,它还代表了一种运算协议(computation protocol)或者称为运算模型好点,如IO[T],Option[T]。运算模型规范了运算值T的运算方式。而Monad是一种特殊的FP运算模型M[A],它是一种持续运算模式。通过flatMap作为链条把前后两个运算连接起来。多个flatMap同时作用可以形成一个程序运行链。我们可以在flatMap函数实现中增加一些附加作用,如维护状态值(state value)、跟踪记录(log)等。
在上一篇讨论中我们用一个Logger的实现例子示范了如何在flatMap函数实现过程中增加附加作用;一个跟踪功能(logging),我们在F[T]运算结构中增加了一个String类型值作为跟踪记录(log)。在本篇讨论中我们首先会对上篇的Logger例子进行一些log类型的概括,设计一个新的Logger结构:
1 case class Logger[LOG, A](log: LOG, value: A) { 2 def map[B](f: A => B): Logger[LOG,B] = Logger(log, f(value)) 3 def flatMap[B](f: A => Logger[LOG,B])(implicit M: Monoid[LOG]): Logger[LOG,B] = { 4 val nxLogger = f(value) 5 Logger(log |+| nxLogger.log, nxLogger.value) 6 } 7
8 }
以上Logger对LOG类型进行了概括:任何拥有Monoid实例的类型都可以,能够支持Monoid |+|操作符号。这点从flatMap函数的实现可以证实。
当然我们必须获取Logger的Monad实例才能使用for-comprehension。不过由于Logger有两个类型参数Logger[LOG,A],我们必须用type lambda把LOG类型固定下来,让Monad运算只针对A类型值:
1 object Logger { 2 implicit def toLogger[LOG](implicit M: Monoid[LOG]) = new Monad[({type L[x] = Logger[LOG,x]})#L] { 3 def point[A](a: => A) = Logger(M.zero,a) 4 def bind[A,B](la: Logger[LOG,A])(f: A => Logger[LOG,B]): Logger[LOG,B] = la flatMap f 5 } 6 }
有了Monad实例我们可以使用for-comprehension:
1 def enterInt(x: Int): Logger[String, Int] = Logger("Entered Int:"+x, x) 2 //> enterInt: (x: Int)Exercises.logger.Logger[String,Int]
3 def enterStr(x: String): Logger[String, String] = Logger("Entered String:"+x, x) 4 //> enterStr: (x: String)Exercises.logger.Logger[String,String]
5
6 for { 7 a <- enterInt(3) 8 b <- enterInt(4) 9 c <- enterStr("Result:") 10 } yield c + (a * b).shows //> res0: Exercises.logger.Logger[String,String] = Logger(Entered Int:3Entered I 11 //| nt:4Entered String:Result:,Result:12)
不过必须对每个类型定义操作函数,用起来挺不方便的。我们可以为任何类型注入操作方法:
1 final class LoggerOps[A](a: A) { 2 def applyLog[LOG](log: LOG): Logger[LOG,A] = Logger(log,a) 3 } 4 implicit def toLoggerOps[A](a: A) = new LoggerOps[A](a) 5 //> toLoggerOps: [A](a: A)Exercises.logger.LoggerOps[A]
我们为任意类型A注入了applyLog方法:
1 3.applyLog("Int three") //> res1: Exercises.logger.Logger[String,Int] = Logger(Int three,3)
2 "hi" applyLog "say hi" //> res2: Exercises.logger.Logger[String,String] = Logger(say hi,hi)
3 for { 4 a <- 3 applyLog "Entered Int 3"
5 b <- 4 applyLog "Entered Int 4"
6 c <- "Result:" applyLog "Entered String 'Result'"
7 } yield c + (a * b).shows //> res3: Exercises.logger.Logger[String,String] = Logger(Entered Int 3Entered 8 //| Int 4Entered String 'Result',Result:12)
用aplyLog这样操作方便多了。由于LOG可以是任何拥有Monoid实例的类型。除了String类型之外,我们还可以用Vector或List这样的高阶类型:
1 for { 2 a <- 3 applyLog Vector("Entered Int 3") 3 b <- 4 applyLog Vector("Entered Int 4") 4 c <- "Result:" applyLog Vector("Entered String 'Result'") 5 } yield c + (a * b).shows //> res4: Exercises.logger.Logger[scala.collection.immutable.Vector[String],Str 6 //| ing] = Logger(Vector(Entered Int 3, Entered Int 4, Entered String 'Result') 7 //| ,Result:12)
一般来讲,用Vector效率更高,在下面我们会证实这点。
既然A可以是任何类型,那么高阶类型如Option[T]又怎样呢:
1 for {
2 oa <- 3.some applyLog Vector("Entered So