上两期我们讨论了Monad。我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型。任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Monad实例,就可以使用Monad组件库像for-comprehension这样特殊的、Monad具备的泛函式数据结构内部的按序计算运行流程。针对不同的数据类型,flatMap+unit组件实现方式会有所不同,这是因为flatMap+unit代表着承载数据类型特别的计算行为。之前我们尝试了List,Option,甚至更复杂的State等数据类型的Monad实例,过程中我们分别对这些数据类型的unit和flatMap进行了实现。实际上flatMap+unit并不是Monad唯一的最基本组件函数,还有compose+unit及join+map+unit这两组Monad最基本组件函数,因为我们可以用这些组件相互实现:
trait Monad[M[_]] { def unit[A](a: A): M[A] def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B] def compose[A,B,C](f: A => M[B], g: B => M[C]): A => M[C] = { a => { flatMap(f(a))(g)} } def flatMapByCompose[A,B](ma: M[A])(f: A => M[B]): M[B] = { compose(((_):Unit) => ma,f)(()) } def join[A](mma: M[M[A]]): M[A] = { flatMap(mma)(ma => ma) } def map[A,B](ma: M[A])(f: A => B): M[B] = { flatMap(ma)(a => unit(f(a))) } def flatMapByJoin[A,B](ma: M[A])(f: A => M[B]): M[B] = { join(map(ma)(a => f(a))) } def composeByJoin[A,B,C](f: A => M[B], g: B => M[C]): A => M[C] = { a => { join(map(f(a))(g)) } } }
Monad也是Functor,因为我们可以用flatMap+unit来实现map。现在我们可以把Monad trait 改成 extends Functor:
trait Functor[F[_]] { def map[A,B](fa: F[A])(f: A => B): F[B] } trait Monad[M[_]] extends Functor[M]{ def unit[A](a: A): M[A] def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B] def map[A,B](ma: M[A])(f: A => B): M[B] = { flatMap(ma)(a => unit(f(a))) }
def map2[A,B,C](ma: M[A], mb: M[B])(f: (A,B) => C): M[C] = { flatMap(ma)(a => map(mb)(b => f(a,b))) }
def sequence[A](lma: List[M[A]]): M[List[A]] = { lma.foldRight(unit(List[A]()))((a,ma) => map2(a,ma)(_ :: _)) } def traverse[A,B](la: List[A])(f: A => M[B]): M[List[B]] = { la.foldRight(unit(List[B]()))((a,mb) => map2(f(a),mb)(_ :: _)) }
def Map2[A,B,C](ma: Option[A], mb: Option[B])(f: (A,B) => C): Option[C] = { (ma,mb) match { case (Some(a),Some(b)) => Some(f(a,b)) case _ => None } }
因为我们能够用flatMap来实现map2,所以Monad就是Applicative。但反之Applicative不一定是Monad。既然我们希望提高泛函施用模式的效率,那我们就先从函数施用开始。先看看map,map2,flatMap这三个函数:
def map[A,B] (ma: M[A]) (f: A => B) : M[B] def map2[A,B,C](ma: M[A], mb: M[B])(f: (A,B) => C): M[C] def flatMap[A,B] (ma: M[A]) (f: A => M[B]) : M[B]
map和map2都是正宗的在高阶数据类型结构内的函数施用,但flatMap的函数是 A=>M[B],会破坏结果的结构。例如:我们对一个有3个元素的List进行map操作,结果仍然是一个3个元素的List。但如果flatMap的话就可能会产生不同长度的List:
既然是更专注于函数施用,那么还有一种款式的函数是值得研究的:
def apply[A,B](fab: F[A => B])(fa: F[A]): F[B]
trait Applicative[F[_]] extends Functor[F] { def unit[A](a: A): F[A] def map2[A,B,C](fa: F[A], fb: F[B])(f: (A,B) => C): F[C] = { apply(fb)(map(fa)(f.curried)) //map(fa)(a => (b => c)) >>> F[A=>B] } def apply[A,B](fa: F[A])(fab: F[A =>B]): F[B] = { // map2(fab,fa)((f,a) => f(a)) map2(fab,fa)(_(_)) } def map[A,B](fa: F[A])(f: A => B): F[B] = { // map2(unit(f),fa)((f,a) => f(a)) map2(unit(f),fa)(_(_)) } def mapByApply[A,B](fa: F[A])(f: A => B): F[B] = { apply(fa)(unit(f)) } }
def Map2[A,B,C](ma: Option[A], mb: Option[B])(f: (A,B) => C): Option[C] = { (ma,mb) match { case (Some(a),Some(b)) => Some(f(a,b)) case _ => None } } def apply[A,B](ma: Option[A])(f: Option[A => B]): Option[B] = { (ma,f) match { case (Some(a),Some(f)) => Some(f(a)) case _ => None } } def flatMap[A,B](ma: Option[A])(f: A => Option[B]): Option[B] = { ma match { case Some(a) => f(a) case _ => None } }
apply和map的运算都依赖于两个传入参数的状态:只有两个参数都是Some时才会在Some结构内部进行运算。而flatMap的传入函数A=>Option[B]是否运行则依赖于ma状态是否Some,而传入函数运行的结果又依赖于ma内元素A的值。所以我们确定Applicative可以保持运算结果的结构不变,而Monad有可能会造成运算结果的结构变化。
我们知道可以用map2把两个Monatic值M[A],M[B]施用函数(A+B)=>C连接起来,概括这个模式map3,map4,map5...都可以起到相同作用:
我们曾经用map2实现过map3,map4,map5:
def map3[A,B,C,D](ma: M[A], mb: M[B], mc: M[C])(f: (A,B,C) => D): M[D] = { map2(ma, map2(mb,mc){(b,c) => (b,c)} ){(a,bc) => { val (b,c) = bc f(a,b,c) }} } def map4[A,B,C,D,E](ma: M[A], mb: M[B], mc: M[C], md: M[D])(f: (A,B,C,D) => E): M[E] = { map2(ma, map2(mb, map2(mc,md){(c,d) => (c,d)} ){(b,cd) => (b,cd)} ){(a,bcd) => { val (b,(c,d)) = bcd f(a,b,c,d) }} } def map5[A,B,C,D,E,F](ma: M[A], mb: M[B], mc: M[C], md: M[D], me: M[E])(f: (A,B,C,D,E) => F): M[F] = { map2(ma, map2(mb, map2(mc, map2(md,me){(d,e) => (d,e)} ){(c,de) => (c,de)} ){(b,cde) => (b,cde)} ){(a,bcde) => { val (b,(c,(d,e))) = bcde f(a,b,c,d,e) }} }
首先我们可以把一个三个入参数的函数curry一下:f(A,B,C) >>> A => B => C => D >>> f.curried。然后把函数放到unit里:
unit(f.curried) = M[A=>B=>C]。apply(M[A])(M[A=>B]):M[B]。我们可以针对每个M值分步施用apply:A=>B=>C >>> A=>BC >>> BC=B=>C,apply(M[A])(unit(f.curried))=M[B=>C],那么可以用apply来实现map3,map4,map5:
def map3[A,B,C,D](ma: F[A], mb: F[B], mc: F[C])(f: (A,B,C) => D): F[D] = { apply(mc)(apply(mb) (apply(ma)(unit(f.curried)))) } def map4[A,B,C,D,E](ma: F[A], mb: F[B], mc: F[C],md: F[D])(f: (A,B,C,D) => E): F[E] = { apply(md)(apply(mc) (apply(mb) (apply(ma)(unit(f.curried))))) } def map5[A,B,C,D,E,G](ma: F[A], mb: F[B], mc: F[C],md: F[D], me: F[E])(f: (A,B,C,D,E) => G): F[G] = { apply(me)(apply(md) (apply(mc) (apply(mb) (apply(ma)(unit(f.curried)))))) }
因为我们可以用flatMap来实现map2和apply,所以所有Monad都是Applicative。由于我们在Monad组件库里已经实现许多有用的组件函数,我们就不需要在Applicative库里重复了。我们可以对Monad extends Applicative:
trait Monad[M[_]] extends Applicative[M]{ def unit[A](a: A): M[A] def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B] = { join(map(ma)(f)) } def compose[A,B,C](f: A => M[B], g: B => M[C]): A => M[C] = { a => { flatMap(f(a))(g)} } def join[A](mma: M[M[A]]): M[A] = { flatMap(mma)(ma => ma) } override def apply[A,B](ma: M[A])(fab: M[A => B]): M[B] = { flatMap(fab)(f => flatMap(ma)(a => unit(f(a)))) }
这样所有Monad都可以是Applicative。但是,有些Applicative未必是Monad,因为我们可能无法用某些类型Applicative实例的map2或apply来实现flatMap、join、compose。
我们还是用一个实际的例子来解释Monad与Applicative的不同行为:
如果我们设计一个网页的登陆页面,用户需要填写name,birthdate,phone三个字段。提交页面后由系统验证录入信息。我们前面使用过类型Either,刚好用来返回系统验证结果。系统需要对三个字段进行验证,我们可以先把这三个验证函数的款式写出来:
implicit def eitherMonad[E] = new Monad[({type l[V] = Either[E,V]})#l] { def unit[A](a: A) = Right(a) def flatMap[A,B](ea: Either[E,A])(f: A => Either[E,B]): Either[E,B] = { ea match { case Right(a) => f(a) case Left(e) => Left(e) } } } def validateName(name: String): Either[String,String] def validatebirthdate(birthdate: Date): Either[String,Date] def validatePhone(phone: String): Either[String,String]
这三个验证函数都返回Either类型。因为有implict eitherMonad实例所以可以flatMap验证函数的结果:
validateName(field1) flatMap (f1 => validateBirthdate(field2) flatMap (f2 => validatePhone(field3) map (WebForm(_, _, _))
apply(apply(apply((WebForm(_, _, _)).curried)( validateName(field1)))( validateBirthdate(field2)))( validatePhone(field3))
我们继续把这个例子推进下去:我们希望系统一次性运行所有验证函数。如果出现一或多个错误就同时返回所有错误信息。由于可能需要返回多条错误信息,Either类型已经不足以用了。我们试着加一个新的错误处理数据类型:
trait Validation[+E,+A] case class Failure[E](head: E, tail: Vector[E]) extends Validation[E,Nothing] case class success[A](a: A) extends Validation[Nothing,A]
我们先看看Applicative实例:
implicit def validationApplicative[E] = new Applicative[({type l[A] = Validation[E,A]})#l] { def unit[A](a: A) = Success(a) def map2[A,B,C](fa: Validation[E,A], fb: Validation[E,B])(f: (A,B) => C): Validation[E,C] = { (fa,fb) match { case (Success(a),Success(b)) => Success(f(a,b)) case (Failure(h1,t1),Failure(h2,t2)) => Failure(h1, t1 ++ Vector(h2) ++ t2) case (e@Failure(_,_),_) => e case (_,e@Failure(_,_)) => e } } }
我们接着完成这个validateWebForm函数:
trait Validation[+E,+A] case class Failure[E](head: E, tail: Vector[E]) extends Validation[E,Nothing] case class Success[A](a: A) extends Validation[Nothing,A] implicit def validationApplicative[E] = new Applicative[({type l[A] = Validation[E,A]})#l] { def unit[A](a: A) = Success(a) def map2[A,B,C](fa: Validation[E,A], fb: Validation[E,B])(f: (A,B) => C): Validation[E,C] = { (fa,fb) match { case (Success(a),Success(b)) => Success(f(a,b)) case (Failure(h1,t1),Failure(h2,t2)) => Failure(h1, t1 ++ Vector(h2) ++ t2) case (e@Failure(_,_),_) => e case (_,e@Failure(_,_)) => e } } } import java.util.Date case class WebForm(name: String, birthdate: Date, phone: String) def validateName(name: String): Validation[String, String] = { if (name != "") Success(name) else Failure("Name cannot be empty", Vector()) } def validateBirthdate(birthdate: String): Validation[String, Date] = { try { import java.text._ Success((new SimpleDateFormat("yyyy-MM-dd")).parse(birthdate)) } catch { case e => Failure("Birthdate must be in the form yyyy-MM-dd", Vector()) } } def validatePhone(phoneNumber: String): Validation[String, String] = { if (phoneNumber.matches("[0-9]{10}")) Success(phoneNumber) else Failure("Phone number must be 10 digits", Vector()) } def validateWebForm(name: String, birthdate: String, phone: String): Validation[String, WebForm] = { apply(validateName(name)) (apply(validateBirthdate(birthdate)) (apply(validatePhone(phone))((WebForm(_,_,_)).curried))))) }