本章將學(xué)習(xí)相關(guān)的抽象淆两,可應(yīng)用函子住闯,雖然沒有Monad強(qiáng)大瓜浸,但是更普遍(因此通用)。在尋找可應(yīng)用函子的過程中比原,也展示了如何發(fā)現(xiàn)這種抽象并利用這種方式發(fā)現(xiàn)另外一種有用的抽象插佛,可遍歷函子。這些抽象需要一些時(shí)間去融匯貫通量窘,但稍加注意的話雇寇,將會(huì)發(fā)現(xiàn)在日常函數(shù)式編程中它是不斷出現(xiàn)的。
泛化單子
至此已經(jīng)看到不同的操作蚌铜,比如sequence和traverse锨侯,它們在不同monad中被實(shí)現(xiàn)了多次。上一章泛化了這個(gè)實(shí)現(xiàn)冬殃,讓他們對任何monad F都有效:
def sequence[A](li: List[F[A]]): F[List[A]] =
traverse(li){fa => fa}
def traverse[A, B](la: List[A])(f: A => F[B]): F[List[B]] =
la.foldLeft(unit(List[B]())){(b, a) => map2(f(a), b)(_ :: _)}
可能沒有注意到很多monad組合是可以使用unit和map2來定義的囚痴。組合traverse是一個(gè)例子,他沒有直接調(diào)用flatMap审葬,以至于我們考慮map2可以作為原語深滚。采用unit和map2作為原語,我們將獲得另外一個(gè)抽象涣觉,這個(gè)抽象被稱為可應(yīng)用函子成箫,雖然沒有monad強(qiáng)大,但也會(huì)帶來一些額外的功能旨枯。
Applicative trait
Applicative函子可以被捕捉為一個(gè)接口蹬昌,Applicative中原語包含map2和unit。
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]
def map[A, B](fa: F[A])(f: A => B): F[B] =
map2(fa, unit(())){(a, _) => f(a)}
def traverse[A, B](li: List[A])(f: A => F[B]): F[List[B]] =
li.foldLeft(unit(List[B]())){(b, a) => map2(f(a), b){_ :: _}}
}
練習(xí) 12.1
盡可能多地將monad組合子移到Applicative組合子攀隔,只使用map2和unit函數(shù)皂贩,或者根據(jù)這兩個(gè)實(shí)現(xiàn)的其它方法。
def sequence[A](lma: List[F[A]]): F[List[A]] =
traverse(lma){a => a}
def replicateM[A](n: Int, fa: F[A]): F[List[A]] =
sequence(List.fill(n)(fa))
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
map2(fa, fb)((_, _))
練習(xí) 12.2
可應(yīng)用(applicative)這個(gè)名詞源于這樣一個(gè)事實(shí)昆汹,它使用另外一套原語unit和apply函數(shù)來表達(dá)Applicative接口明刷,而非unit和Map2函數(shù),可以只用unit和apply來定義map2和map函數(shù)满粗。
trait Applicative[F[_]] extends Functor[F] {
def unit[A](a: => A): F[A]
def apply[A, B](fab: F[A => B])(fa: F[A]): F[B]
def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = {
val fabc: F[A => B => C] = unit(f.curried)
val fbc = apply(fabc)(fa)
apply(fbc)(fb)
}
def map[A, B](fa: F[A])(f: A => B): F[B] =
map2(fa, unit(())){(a, _) => f(a)}
}
練習(xí) 12.3
apply方法對實(shí)現(xiàn)map3和map4等方法也是很有用的辈末,實(shí)現(xiàn)map3和map4方法只使用unit和apply。
def map3[A, B, C, D](fa: F[A], fb: F[B], fc: F[C])(f: (A, B, C) => D): F[D] = {
val fabcd = unit(f.curried)
val fbcd = apply(fabcd)(fa)
val fcd = apply(fbcd)(fb)
apply(fcd)(fc)
}
def map4[A, B, C, D, E](fa: F[A], fb: F[B], fc: F[C],
fd: F[D])(f: (A, B, C, D) => E): F[E] = {
val fabcde = unit(f.curried)
val fbcde = apply(fabcde)(fa)
val fcde = apply(fbcde)(fb)
val fde = apply(fcde)(fc)
apply(fde)(fd)
}
更進(jìn)一步可以Monad[F]成為Applicative[F]的一個(gè)子類,并使用flatMap實(shí)現(xiàn)map2:
trait Monad[F[_]] extends Applicative[F] {
def unit[A](a: => A): F[A]
def flatMap[A, B](a: F[A])(f: A => F[B]): F[B]
def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C] =
a => join(map(f(a))(g))
def join[A](mma: F[F[A]]): F[A] =
flatMap(mma){ma => ma}
override def map[A, B](a: F[A])(f: (A) => B): F[B] =
flatMap(a)(a => unit(f(a)))
override def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] =
flatMap(fa){a =>
map(fb){b =>
f(a, b)
}
}
}
單子和可應(yīng)用函子的區(qū)別
雖然所有的monad都是可應(yīng)用函子挤聘,但是并不是所有的applicative都是單子轰枝。
可應(yīng)用函子的優(yōu)勢
一般來說,實(shí)現(xiàn)一個(gè)像traverse的組合子需要的假設(shè)越少越好组去。
因?yàn)锳pplicative相比Monad弱一些鞍陨,這就給了它更多的靈活性。
Applicative functor可組合从隆,但是一般Monad不可以诚撵。
練習(xí) 12.6
為Validation寫一個(gè)Applicative實(shí)例,累計(jì)失敗時(shí)的錯(cuò)誤键闺。注意缩筛,在失敗的情況下净嘀,至少會(huì)有一個(gè)error存在于列表的head吃型,其余的error累加在列表的tail霹菊。
sealed trait Validation[+E, +A] {
}
case class Failure[E](head: E, tail: Vector[E] = Vector()) extends Validation[E, Nothing]
case class Success[A](a: A) extends Validation[Nothing, A]
object Applicative {
def apply[F[_]](implicit fa: Applicative[F]): Applicative[F] = fa
type StringValidation[A] = Validation[String, A]
implicit def validtionApplicative: Applicative[StringValidation] = new Applicative[StringValidation] {
override def map2[A, B, C](fa: StringValidation[A],
fb: StringValidation[B])(f: (A, B) => C): StringValidation[C] = (fa, fb) match {
case (Failure(h, t), Success(b)) => Failure(h, t)
case (Success(a), Failure(h, t)) => Failure(h, t)
case (Success(a), Success(b)) => Success(f(a, b))
case (Failure(h1, t1), Failure(h2, t2)) => Failure(h2, t2 ++ Vector(h1) ++ t1)
}
override def unit[A](a: => A): StringValidation[A] = Success(a)
}
def validName(name: String): Validation[String, String] =
if (name != "") Success(name)
else Failure("Name can not be empty")
def validBirthdate(birthdate: String): Validation[String, Date] =
try {
import java.text._
Success(new SimpleDateFormat("yyyy-MM-dd").parse(birthdate))
} catch {
case _ => Failure("Birthdate must be in the form yyyy-MM-dd")
}
def validPhone(phoneNum: String): Validation[String, String] =
if (phoneNum.matches("[0-9]{10}")) Success(phoneNum)
else Failure("Phone number must be 10 digits")
case class WebForm(name: String, birthdate: Date, phoneNum: String)
def vaildWebForm(name: String, birthdate: String,
phoneNum: String)(implicit va: Applicative[StringValidation]): Validation[String, WebForm] =
va.map3(validName(name), validBirthdate(birthdate), validPhone(phoneNum))(WebForm(_, _, _))
}
可遍歷函子
前面的內(nèi)容介紹了可應(yīng)用函子伤提,我們再次來查看traverse和sequence的函數(shù)簽名:
def traverse[F[_], A, B](la: List[A])(f: A => F[B]): F[List[B]]
def sequence[F[_], A](la: List[F[A]]): F[List[A]]
在這些函數(shù)簽名中我們看到了具體的類型構(gòu)造器List癞己,除了List還有不少數(shù)據(jù)類型也是可以折疊的台囱,那么這些數(shù)據(jù)類型是不是可遍歷的菠隆?當(dāng)然氏淑!但是為每個(gè)可遍歷的數(shù)據(jù)類型編寫專門的sequence和遍歷方法是十分繁瑣的勃蜘。需要一個(gè)新的接口,我們稱為Traverse:
trait Traverse[F[_]] extends Functor[F] {
def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(implicit ga: Applicative[G]): G[F[B]] =
sequence(map(fa)(f))
def sequence[G[_], A](fga: F[G[A]])(implicit ga: Applicative[G]): G[F[A]] =
traverse(fga)(a => a)
}
練習(xí) 12.13
對List假残、Option和Tree實(shí)現(xiàn)Traverse實(shí)例
object Traverse {
def apply[F[_]](implicit tf: Traverse[F]): Traverse[F] = tf
implicit val listTraverse: Traverse[List] = new Traverse[List] {
override def map[A, B](a: List[A])(f: (A) => B): List[B] = a match {
case Nil => Nil
case head :: tail => f(head) :: map(tail)(f)
}
}
implicit val optionTraverse: Traverse[Option] = new Traverse[Option] {
override def map[A, B](a: Option[A])(f: (A) => B): Option[B] = a match {
case None => None
case Some(a1) => Some(f(a1))
}
}
implicit val treeTraverse: Traverse[Tree] = new Traverse[Tree] {
override def map[A, B](a: Tree[A])(f: (A) => B): Tree[B] =
Tree(f(a.head), a.tail.map(t => map(t)(f)))
}
}
case class Tree[+A](head: A, tail: List[Tree[A]])