我们再次审视一下以前了解过的FP函数施用方法,

1   def =<<(implicit m: Bind[M]): M[B] = m.bind
 1 trait KeyLog[K] {
 2   def value: K
 3   def log: String
 4   override def toString = "["+value+","+log+"]"
 5 }
 6 object KeyLog {
 7     def apply[K](k: K, msg: String): KeyLog[K] = new KeyLog[K] {
 8         def value = k
 9         def log = msg
10     }
11 }
12 
13 KeyLog(3,"Entered Number 3")                      //> res0: Exercises.keylog.KeyLog[Int] = [3,Entered Number 3]
14 KeyLog("Hello", "Entered String 'Hello'")         //> res1: Exercises.keylog.KeyLog[String] = [Hello,Entered String 'Hello']
1 trait ApplicativePlus[F[_]] extends Applicative[F] with PlusEmpty[F] { self =>

实际上scalar提供了Writer数据结构,它是WriterT类型的一个特例:

 1 //Kleisli款式函数kf,kg 2 val kf: Int => Option[String] =  => Some((i + 3).shows) 3                                                   //> kf  : Int => Option[String] = <function1> 4 val kg: String => Option[Boolean] = { case "3" => true.some; case _ => false.some } 5                                                   //> kg  : String => Option[Boolean] = <function1> 6 //Kleisli函数组合操作 7 import Kleisli._ 8 val kfg = kleisli >=> kleisli             //> kfg  : scalaz.Kleisli[Option,Int,Boolean] = Kleisli(<function1>) 9 kfg(1)                                            //> res5: Option[Boolean] = Some10 kfg(0)                                            //> res6: Option[Boolean] = Some
 1 for { //list
 2     a <- 1 |-> 50 if a.shows contains '7'
 3 } yield a                                         //> res5: List[Int] = List(7, 17, 27, 37, 47)
 4 for { //option
 5     a <- Some(3)
 6     b <- Some(4) if a < b
 7 } yield b                                         //> res6: Option[Int] = Some(4)
 8 for { //option
 9     a <- Some(3)
10     b <- Some(4) if a > b
11 } yield b                                         //> res7: Option[Int] = None

ApplicativePlus又继承了PlusEmpty:scalaz/PlusEmpty.scala

 1 def enterInt: Logger[String, Int] = Logger("Entered Int:"+x, x) 2                                                   //> enterInt: 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 + .shows                         //> res0: Exercises.logger.Logger[String,String] = Logger(Entered Int:3Entered I11                                                   //| nt:4Entered String:Result:,Result:12)

拿操作函数>=>举例:implicit b: Bind[M]明确了M必须是个Monad。

 1 import scalaz._
 2 import Scalaz._
 3 object keylog {
 4 trait KeyLog[+K] {
 5   override def toString = this match {
 6     case KeyIn(value,log) => "["+value+","+log+"]"
 7     case _ => "[Keypad Locked]"
 8   }
 9   def mapLog(preLog: String): KeyLog[K] = this match {
10     case KeyIn(value,log) => KeyIn(value,preLog +";"+log)
11     case _ => KeyLock
12   }
13   def flatMap[I](f: K => KeyLog[I]): KeyLog[I] = this match {
14     case KeyIn(value,log) => f(value).mapLog(log)
15     case _ => KeyLock
16   }
17 }
18 case class KeyIn[K](value: K, log: String) extends KeyLog[K]
19 case object KeyLock extends KeyLog[Nothing]
20 object KeyLog {
21 /*    def apply[K](k: K, msg: String): KeyLog[K] = new KeyLog[K] {
22         def value = k
23         def log = msg
24     } */
25     implicit object keylogMonad extends Monad[KeyLog] {
26         def point[K](k: => K): KeyLog[K] = KeyIn(k,"")
27         def bind[K,I](kk: KeyLog[K])(f: K => KeyLog[I]): KeyLog[I] = kk flatMap f
28     }
29 }

正是我们期待的效果。

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 + .shows                         //> res3: Exercises.logger.Logger[String,String] = Logger(Entered Int 3Entered 8                                                   //| Int 4Entered String 'Result',Result:12)

以上的函数f,g必须满足一定的条件才能实现组合。这个从f或g可以看出:必需固定有一个输入参数及输入参数类型和函数结果类型必需一致,因为一个函数的输出成为另一个函数的输入。在FP里这样的函数组合就是Monadic Reader。

最终log值:"Entered Number 3;Entered String 'Hello'。我们实现了在运算flatMap过程中对log进行的累积。

1 trait KeyLog[K] {2   def value: K3   def log: String4   override def toString = "["+value+","+log+"]"5   def mapLog(preLog: String): KeyLog[K] = KeyLog(value,preLog +";"+log)6   def flatMap[I](f: K => KeyLog[I]): KeyLog[I] =7     f.mapLog8 }

一样可以使用。注意oa,ob是Option类型所以必须使用^{...}来结合它们。

下面我们就用scalaz自带的例子scalaz.example里的KleisliUsage.scala来说明一下Kleisli的具体使用方法吧:

1 List(1,2,3) <+> List(4,5,6)                       //> res4: List[Int] = List(1, 2, 3, 4, 5, 6)
2 Nil <+> List(1,2,3)                               //> res5: List[Int] = List(1, 2, 3)
3 List(1,2,3) <+> Nil                               //> res6: List[Int] = List(1, 2, 3)
4 none <+> 2.some                                   //> res7: Option[Int] = Some(2)
5 2.some <+> 3.some                                 //> res8: Option[Int] = Some(2)
6 2.some <+> none                                   //> res9: Option[Int] = Some(2)
7 none <+> none                                     //> res10: Option[Nothing] = None
 1 for { //list 2     a <- 1 |-> 50 if a.shows contains '7' 3 } yield a                                         //> res5: List[Int] = List(7, 17, 27, 37, 47) 4 for { //option 5     a <- Some(3) 6     b <- Some(4) if a < b 7 } yield b                                         //> res6: Option[Int] = Some 8 for { //option 9     a <- Some(3)10     b <- Some(4) if a > b11 } yield b                                         //> res7: Option[Int] = None

不过必须对每个类型定义操作函数,用起来挺不方便的。我们可以为任何类型注入操作方法:

 1   val allCountry = kleisli(continents) >==> countries 2                                                   //> allCountry  : scalaz.Kleisli[List,String,Exercises.kli.Country] = Kleisli(< 3                                                   //| function1>) 4   val allCity = kleisli(continents) >==> countries >==> cities 5                                                   //> allCity  : scalaz.Kleisli[List,String,Exercises.kli.City] = Kleisli(<functi 6                                                   //| on1>) 7   allCountry("Amer")                              //> res10: List[Exercises.kli.Country] = List(Country(USA,List(City(Washington, 8                                                   //| false,20), City(New York,false,20)))) 9   allCity("Amer")                                 //> res11: List[Exercises.kli.City] = List(City(Washington,false,20), City(New 10                                                   //| York,false,20))

守卫函数按要求对KeyLog状态进行了过滤。

从字面上看是希望通过守卫函数过滤数字为0的数字。等等,enterInt已经确定了输入为4,是 > 0,还过滤什么?不是找事吗。所以我们的目的应该聚焦在过滤需求上。Scalaz为List,Option提供了MonadPlus实例,我们看看这两种类型的守卫函数使用:

1 case class Logger[LOG, A](log: LOG, value: A) {2     def map[B](f: A => B): Logger[LOG,B] = Logger(log, f3     def flatMap[B](f: A => Logger[LOG,B])(implicit M: Monoid[LOG]): Logger[LOG,B] = {4         val nxLogger = f5         Logger(log |+| nxLogger.log, nxLogger.value)6     }7       8 }

下面是一组地理信息结构:

正是我们期待的效果。

1 for {2  a <- enterInt(3)3  b <- enterInt(4)  if b > 04  c <- enterStr("Result:")5 } yield c + .toString

既然A可以是任何类型,那么高阶类型如Option[T]又怎样呢:

Monad Reader就是一种函数的组合。在scalaz里函数本身就是Monad,自然也就是Functor和applicative。我们可以用Monadic方法进行函数组合:

 

1   implicit val optionInstance = new Traverse[Option] with MonadPlus[Option] with Each[Option] with Index[Option] with Length[Option] with Cozip[Option] with Zip[Option] with Unzip[Option] with Align[Option] with IsEmpty[Option] with Cobind[Option] with Optional[Option] {2 ...3     def empty[A]: Option[A] = None4     def plus[A](a: Option[A], b: => Option[A]) = a orElse b5 ...

当然我们必须获取Logger的Monad实例才能使用for-comprehension。不过由于Logger有两个类型参数Logger[LOG,A],我们必须用type lambda把LOG类型固定下来,让Monad运算只针对A类型值:

无论函数款式或者类封套(List本来就是Monad)都适合Kleisli。我们可以用Kleisli把这三个局部函数用各种方法组合起来实现更广泛功能:

现在我们可以先获取KeyLog的Monad实例,然后进行flatMap串联及使用for-comprehension进行行令编程了:

如果从flatMap代表持续算法这个角度分析:flatMap实际连接了两个算法F[A] => F[B]。我们应该可以在运算flatMap的过程中实现一些附加的效果。这个要求应该可以在实现flatMap函数时做到。我们这篇讨论的重点就是在示范如何在实现flatMap时增加一些效果。当把一串算法用flatMap链接起来时这些附加效果是如何积累的。

value)。这是一种典型的FP状态维护模式。不过WriterT的这个是在运算模型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.andThen3 4   def flatMapF[B](f: A => F[(implicit F: Bind[F], s: Semigroup[W]): WriterT[F, W, B] =5     writerT(F.bind{wa =>6       val z = f7       F.map(wb => (s.append(wa._1, wb._1), wb._2))8     })

在flatMapF函数里对的W进行了Monoid append操作。

实际上Writer可以说是一种附加的数据结构,它在运算模型F[A]内增加了一个状态值W形成了F这种形式。当我们为任何类型A提供注入方法来构建这个Writer结构后,任意类型的运算都可以使用Writer来实现在运算过程中增加附加作用如维护状态、logging等等。我们看看scalaz/Syntax/WriterOps.scala:

package scalazpackage syntaxfinal class WriterOps[A] {  def set[W]: Writer[W, A] = WriterT.writer(w -> self)  def tell: Writer[A, Unit] = WriterT.tell}trait ToWriterOps {  implicit def ToWriterOps[A] = new WriterOps}

存粹是方法注入。现在任何类型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,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) 7 3.some set List("some 3")                         //> res5: scalaz.Writer[List[String],Option[Int]] = WriterT((List,Some(3 8                                                   //| ))) 9 Vector("just say hi").tell                        //> res6: scalaz.Writer[scala.collection.immutable.Vector[String],Unit] = Writer10                                                   //| 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 "Entered String 'Result'"5 } yield c + .shows                         //> res7: scalaz.WriterT[scalaz.Id.Id,String,String] = WriterT((Entered Int 3 En6                                                   //| tered Int 4 Entered String 'Result',Result:12))

如果A是高阶类型如List[T]的话,还能使用吗:

1 for {2  la <- List(1,2,3) set Vector("Entered List")3  lb <- List(4,5) set Vector("Entered List")4  lc <- List(6) set Vector("Entered List")5 } yield (la |@| lb |@| lc) {_ + _ + _}            //> res1: scalaz.WriterT[scalaz.Id.Id,scala.collection.immutable.Vector[String]6                                                   //| ,List[Int]] = WriterT((Vector(Entered List, Entered List, Enter7                                                   //| ed List,List(11, 12, 12, 13, 13, 14)))

的确没有问题。

那个gcd例子还是挺有代表性的,我们用Writer来运算和跟踪gcd运算:

 1 def gcd(a: Int, b: Int): Writer[Vector[String],Int] = 2   if (b == 0 ) for { 3       _ <- Vector("Finished at "+a.shows).tell 4   } yield a 5   else 6    Vector(a.shows+" mod "+b.shows+" = "+.shows).tell >>= {_ => gcd(b,a % b)} 7                                                   //> gcd: (a: Int, b: Int)scalaz.Writer[Vector[String],Int] 8  9 gcd(8,3)                                          //> res8: scalaz.Writer[Vector[String],Int] = WriterT((Vector(8 mod 3 = 2, 3 mo10                                                   //| d 2 = 1, 2 mod 1 = 0, Finished at 1),1))11 gcd(16,4)                                         //> res9: scalaz.Writer[Vector[String],Int] = WriterT((Vector(16 mod 4 = 0, Fin12                                                   //| ished at 4),4))

在维护跟踪记录时使用Vector会比List更高效。我们来证明一下:

 1 def listLogCount: Writer[List[String],Unit] = { 2   @annotation.tailrec 3   def countDown(c: Int, w: Writer[List[String],Unit]): Writer[List[String],Unit] = c match { 4       case 0 => w >>= {_ => List("0").tell } 5       case x => countDown(x-1, w >>= {_ => List.tell }) 6   } 7   val t0 = System.currentTimeMillis 8   val r = countDown(c,List[String] 9   val t1 = System.currentTimeMillis10   r >>= {_ => List.shows+"msec").tell }11 }                                                 //> listLogCount: scalaz.Writer[List[String],Unit]12 def vectorLogCount: Writer[Vector[String],Unit] = {13   @annotation.tailrec14   def countDown(c: Int, w: Writer[Vector[String],Unit]): Writer[Vector[String],Unit] = c match {15       case 0 => w >>= {_ => Vector("0").tell }16       case x => countDown(x-1, w >>= {_ => Vector.tell })17   }18   val t0 = System.currentTimeMillis19   val r = countDown(c,Vector[String]20   val t1 = System.currentTimeMillis21   r >>= {_ => Vector.shows+"msec").tell }22 }                                                 //> vectorLogCount: scalaz.Writer[Vector[String],Unit]23 24 (listLogCount(10000).run)._1.last                 //> res10: String = 361msec25 (vectorLogCount(10000).run)._1.last               //> res11: String = 49msec

看,listLogCount用了361msec

vectorLogCount只用了49msec,快了8,9倍呢。

那么如果我想避免使用List(),用Option[List]作为函数输出可以吗?Option是个Monad,第一步可以通过。下一步是把函数款式对齐了:

 1 def enterInt(k: Int): KeyLog[Int] = KeyIn(k, "Number:"+k.toString)
 2                                                   //> enterInt: (k: Int)Exercises.keylog.KeyLog[Int]
 3 def enterStr(k: String): KeyLog[String] = KeyIn(k,"String:"+k)
 4                                                   //> enterStr: (k: String)Exercises.keylog.KeyLog[String]
 5 enterInt(3) >>= {a => enterInt(4) >>= {b => enterStr("Result:") map {c => c + (a * b).toString} }}
 6                                                   //> res0: Exercises.keylog.KeyLog[String] = [Result:12,Number:3;Number:4;String
 7                                                   //| :Result:;]
 8 for {
 9  a <- enterInt(3)
10  b <- enterInt(4)
11  c <- enterStr("Result:")
12 } yield c + (a * b).toString                      //> res1: Exercises.keylog.KeyLog[String] = [Result:12,Number:3;Number:4;String
13                                                   //| :Result:;]

我们试着实现flatMap:

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) = Logger4         def bind[A,B](la: Logger[LOG,A])(f: A => Logger[LOG,B]): Logger[LOG,B] = la flatMap f5     }6 }
 1  val data: List[Continent] = List( 2     Continent("Europe"), 3     Continent("America", 4       List( 5         Country("USA", 6           List( 7             City("Washington"), City("New York"))))), 8     Continent("Asia", 9       List(10         Country("India",11           List(City("New Dehli"), City("Calcutta"))))))

 

List的plus就是把两个List接起来

一般来讲,用Vector效率更高,在下面我们会证实这点。

2、上一个函数输出M[B],他的运算值B就是下一个函数的输入。这就要求下一个函数的输入参数类型必需是B

PlusEmpty定义了抽象成员empty[A],又继承了Plus: scalar/Plus.scala

1 def ap[A,B](mf: F[A => B]): F[B] = mf.flatMap(f => ma.flatMap(a => point  2  def flatMapByJoin[A,B](f: A => M[B]): M[B] = join(a => f3  def join[A](mma: M[M[A]]): M[A]

有了Monad实例我们可以使用for-comprehension:

但是FP里函数运算结果一般都是M[R]这样格式的,所以我们需要对f:A => M[B],g:B => M[C]这样的函数进行组合。这就是scalaz里的Kleisli了。Kleisli就是函数A=>M[B]的类封套,从Kleisli的类定义可以看出:scalaz/Kleisli.scala

在使用for-comprehension时突然想到守卫函数(guard function)。我想既然已经得到了KeyLog的Monad实例,是不是可以在它的for-comprehension里使用守卫函数呢?就像这样:

def enterInt: KeyLog[Int] = KeyLog(k, "Number:"+k.toString)                                                  //> enterInt: Exercises.keylog.KeyLog[Int]def enterStr(k: String): KeyLog[String] = KeyLog(k,"String:"+k)                                                  //> enterStr: (k: String)Exercises.keylog.KeyLog[String]enterInt(3) >>= {a => enterInt(4) >>= {b => enterStr("Result:") map {c => c + (a * b).toString} }}                                                  //> res3: Exercises.keylog.KeyLog[String] = [Result:12,Number:3;Number:4;String:                                                  //| Result:;]for { a <- enterInt(3) b <- enterInt(4) c <- enterStr("Result:")} yield c + .toString                      //> res4: Exercises.keylog.KeyLog[String] = [Result:12,Number:3;Number:4;String                                                  //| :Result:;]
1 type Writer[+W, +A] = WriterT[Id, W, A]

可以看出Kleisli函数组合有着固定的模式:

我们试着用一下flatMap 

先来分析List例子:一个List可能是空的,又可能有多过一个元素,有多种可能。守卫函数的功能就是在这些可能里进行选择。

通过前面的几篇讨论我们了解到F[T]就是FP中运算的表达形式(representation of computation)。在这里F[]不仅仅是一种高阶类型,它还代表了一种运算协议(computationprotocol)或者称为运算模型好点,如IO[T],Option[T]。运算模型规范了运算值T的运算方式。而Monad是一种特殊的FP运算模型M[A],它是一种持续运算模式。通过flatMap作为链条把前后两个运算连接起来。多个flatMap同时作用可以形成一个程序运行链。我们可以在flatMap函数实现中增加一些附加作用,如维护状态值(state value)、跟踪记录等。

 1   def continents(name: String): List[Continent] = 2     data.filter(k => k.name.contains       //> continents: (name: String)List[Exercises.kli.Continent] 3   //查找名字包含A的continent 4   continents("A")                                 //> res7: List[Exercises.kli.Continent] = List(Continent(America,List(Country(U 5                                                   //| SA,List(City(Washington,false,20), City(New York,false,20))))), Continent(A 6                                                   //| sia,List(Country(India,List(City(New Dehli,false,20), City(Calcutta,false,2 7                                                   //| 0)))))) 8   //找到两个:List(America,Asia) 9   def countries(continent: Continent): List[Country] = continent.countries10                                                   //> countries: (continent: Exercises.kli.Continent)List[Exercises.kli.Country]11   //查找America下的国家12   val america =13       Continent("America",14       List(15         Country("USA",16           List(17             City("Washington"), City("New York")))))18                                                   //> america  : Exercises.kli.Continent = Continent(America,List(Country(USA,Lis19                                                   //| t(City(Washington,false,20), City(New York,false,20)))))20   countries                              //> res8: List[Exercises.kli.Country] = List(Country(USA,List(City(Washington,f21                                                   //| alse,20), City(New York,false,20))))22   def cities(country: Country): List[City] = country.cities23                                                   //> cities: (country: Exercises.kli.Country)List[Exercises.kli.City]24   val usa = Country("USA",25             List(26               City("Washington"), City("New York")))27                                                   //> usa  : Exercises.kli.Country = Country(USA,List(City(Washington,false,20), 28                                                   //| City(New York,false,20)))29   cities                                     //> res9: List[Exercises.kli.City] = List(City(Washington,false,20), City(New Y30                                                   //| ork,false,20))

 

它们分别代表了scalaz的三个typeclass。对于FP编程来讲,函数施用(function application)就是改变程序状态,也就是map。那么从map角度分析,如果直接对F[A=>B], A=>F[B]进行map会产生不同的结果类型,如直接map A=>F[B]结果是F[F[B]]。所以我们会想办法把结果类型对齐了,使最终结果类型为F[B]:

我们为任意类型A注入了applyLog方法:

 1 import scalaz._ 2 import Scalaz._ 3 object decompose { 4 //两个测试函数 5 val f =  + 3                              //> f  : Int => Int = <function1> 6 val g =  * 5                              //> g  : Int => Int = <function1> 7 //functor 8 val h = f map g  // f andThen g                   //> h  : Int => Int = <function1> 9 val h1 = g map f  // f compose g                  //> h1  : Int => Int = <function1>10 h(2)  //g                                   //> res0: Int = 2511 h1(2) //f                                   //> res1: Int = 1312 //applicative13 val k = {_ + _}                          //> k  : Int => Int = <function1>14 k(10) // f                              //> res2: Int = 6315 //monad16 val m = g.flatMap{a => f.map(b => a+b)}           //> m  : Int => Int = <function1>17 val n = for {18   a <- f19   b <- g20 } yield a + b                                     //> n  : Int => Int = <function1>21 m(10)                                             //> res3: Int = 6322 n(10)                                             //> res4: Int = 6323 }
1 trait ApplicativePlus[F[_]] extends Applicative[F] with PlusEmpty[F] { self =>
 1 for { 2  a <- enterInt(3) 3  b <- enterInt(4) 4  c <- enterStr("Result:") 5 } yield c + .toString                      //> res3: Exercises.keylog.KeyLog[String] = [Result:12,Number:3;Number:4;String 6                                                   //| :Result:;] 7 for { 8  a <- enterInt(3) 9  b <- enterInt(4) if b > 010  c <- enterStr("Result:")11 } yield c + .toString                      //> res4: Exercises.keylog.KeyLog[String] = [Result:12,Number:3;Number:4;;Strin12                                                   //| g:Result:;]13 for {14  a <- enterInt(3)15  b <- enterInt(4) if b > 516  c <- enterStr("Result:")17 } yield c + .toString                      //> res5: Exercises.keylog.KeyLog[String] = [Keypad Locked]

在上一篇讨论中我们用一个Logger的实现例子示范了如何在flatMap函数实现过程中增加附加作用;一个跟踪功能,我们在F[T]运算结构中增加了一个String类型值作为跟踪记录。在本篇讨论中我们首先会对上篇的Logger例子进行一些log类型的概括,设计一个新的Logger结构:

从continents,countries,cities这三个函数运算结果可以看出它们都可以独立运算。这三个函数的款式如下:

那么现在我们可以使用守卫函数了吧:

现在我们可以把MonadPlus特质混入keylogMonad实例(trait mix-in):

用aplyLog这样操作方便多了。由于LOG可以是任何拥有Monoid实例的类型。除了String类型之外,我们还可以用Vector或List这样的高阶类型:

List[Country] => Option[List[City]]

List和Option的empty分别是:Nil和None,这个很容易理解。那么plus呢?把 plus(list1,list2):list3 = list1 ++ list2这个倒是容易理解,但plus(option1,option2):option3这个又应该怎么理解呢?我们还是看看在scalaz里是怎么定义plus的吧:scalaz.std/List.scala

那么现在我们可以使用守卫函数了吧:

1 for {2     oa <- 3.some applyLog Vector("Entered Some")3     ob <- 4.some applyLog Vector("Entered Some")4 } yield ^{_ * _}                           //> res0: Exercises.logger.Logger[scala.collection.immutable.Vector[String],Opti5                                                   //| on[Int]] = Logger(Vector(Entered Some, Entered Some,Some

本文由必威发布于必威-编程,转载请注明出处:我们再次审视一下以前了解过的FP函数施用方法,

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。