本章将学习相关的抽象,可应用函子,虽然没有Monad强大,但是更普遍(因此通用)。在寻找可应用函子的过程中,也展示了如何发现这种抽象并利用这种方式发现另外一种有用的抽象,可遍历函子。这些抽象需要一些时间去融汇贯通,但稍加注意的话,将会发现在日常函数式编程中它是不断出现的。
泛化单子
至此已经看到不同的操作,比如sequence和traverse,它们在不同monad中被实现了多次。上一章泛化了这个实现,让他们对任何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是一个例子,他没有直接调用flatMap,以至于我们考虑map2可以作为原语。采用unit和map2作为原语,我们将获得另外一个抽象,这个抽象被称为可应用函子,虽然没有monad强大,但也会带来一些额外的功能。
Applicative trait
Applicative函子可以被捕捉为一个接口,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){_ :: _}}
}
练习 12.1
尽可能多地将monad组合子移到Applicative组合子,只使用map2和unit函数,或者根据这两个实现的其它方法。
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)((_, _))
练习 12.2
可应用(applicative)这个名词源于这样一个事实,它使用另外一套原语unit和apply函数来表达Applicative接口,而非unit和Map2函数,可以只用unit和apply来定义map2和map函数。
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)}
}
练习 12.3
apply方法对实现map3和map4等方法也是很有用的,实现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)
}
更进一步可以Monad[F]成为Applicative[F]的一个子类,并使用flatMap实现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)
}
}
}
单子和可应用函子的区别
虽然所有的monad都是可应用函子,但是并不是所有的applicative都是单子。
可应用函子的优势
一般来说,实现一个像traverse的组合子需要的假设越少越好。
因为Applicative相比Monad弱一些,这就给了它更多的灵活性。
Applicative functor可组合,但是一般Monad不可以。
练习 12.6
为Validation写一个Applicative实例,累计失败时的错误。注意,在失败的情况下,至少会有一个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(_, _, _))
}
可遍历函子
前面的内容介绍了可应用函子,我们再次来查看traverse和sequence的函数签名:
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]]
在这些函数签名中我们看到了具体的类型构造器List,除了List还有不少数据类型也是可以折叠的,那么这些数据类型是不是可遍历的?当然!但是为每个可遍历的数据类型编写专门的sequence和遍历方法是十分繁琐的。需要一个新的接口,我们称为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)
}
练习 12.13
对List、Option和Tree实现Traverse实例
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]])