函子:对map函数的泛化
在第一部分和第二部分实现了一些不同组合子库。这些组合子的相似性是值得注意的,比如为每个数据类型都实现了map函数,用于提升某个数据类型到上下文中的数据类型。例如下面的函数签名:
def map[A, B](oa: Option[A])(f: A => B): Option[B] = ???
def map[A, B](op: Parser[A])(f: A => B): Parser[B] = ???
def map[A, B](og: Gen[A])(f: A => B): Gen[B] = ???
这些函数签名只是数据类型不同。这里将它定义为Scala trait并实现map:
trait Functor[F[_]] {
def map[A, B](a: F[A])(f: A => B): F[B]
}
函子法则
无论何时创建一个类似Functor的抽象,都不需要考虑实现哪些抽象方法,而是考虑遵循哪些法则。遵循什么样的法则完全由你决定,Scala不会强加任何这样的法则。对于Functor,将引入的法则是:
map(x)(a => a) == x
Monad:对flatMap和unit函数的泛化
Functor只是众多抽象中的一个。Functor的作用不是那么显著,是因为仅仅使用一个map定义不出太多可用的操作。接下来我们介绍一个有趣的接口,monad。使用monad可以实现很多可用的操作,并且一劳永逸的对重复的代码进行重构。同时相关的法则可以推导库按照预期运行。现在我们为Monad定义一个Scala trait:
trait Monad[F[_]] extends Functor[F] {
def unit[A](a: => A): F[A]
def flatMap[A, B](a: F[A])(f: A => F[B]): F[B]
override def map[A, B](a: F[A])(f: (A) => B): F[B] =
flatMap(a)(a => unit(f(a)))
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)
}
}
}
练习 11.1
val monadOption: Monad[Option] = new Monad[Option] {
override def flatMap[A, B](a: Option[A])(f: (A) => Option[B]): Option[B] = a match {
case None => None
case Some(a) => f(a)
}
override def unit[A](a: => A): Option[A] = Some(a)
}
val monadStream: Monad[Stream] = new Monad[Stream] {
override def flatMap[A, B](a: Stream[A])(f: (A) => Stream[B]): Stream[B] = a match {
case Empty => Empty
case head #:: tail => f(head) ++ flatMap(tail)(f)
}
override def unit[A](a: => A): Stream[A] = Stream(a)
}
val monadList: Monad[List] = new Monad[List] {
override def flatMap[A, B](a: List[A])(f: (A) => List[B]): List[B] = a match {
case Nil => Nil
case head :: tail => f(head) ++ flatMap(tail)(f)
}
override def unit[A](a: => A): List[A] = List(a)
}
val monadPar: Monad[Par] = new Monad[Par] {
override def flatMap[A, B](a: Par[A])(f: (A) => Par[B]): Par[B] =
es => {
val aa = a(es)
f(aa.get())(es)
}
override def unit[A](a: => A): Par[A] = Par.unit(a)
}
Monadic组合子
现在已经有Monad的原始语义了,回看之前的章节是否有其它为Monadic数据类型实现的函数可以统一被实现。
练习 11.3
大家已经很熟悉sequence和traverse的组合了,之前的章节很多地方都实现了它,现在使用Monad[F]来实现它们:
def sequence[A](li: List[F[A]]): F[List[A]] = {
def loop(n: Int, res: F[List[A]]): F[List[A]] = n match {
case m if m < 0 => res
case _ =>
val temp = flatMap(res){la =>
map(li(n)){a =>
a :: la
}
}
loop(n - 1, temp)
}
loop(li.length - 1, unit(Nil))
}
def traverse[A, B](la: List[A])(f: A => F[B]): F[List[B]] =
sequence(la.map(f))
练习 11.4
实现replicateM。
def replicateM[A](n: Int, ma: F[A]): F[List[A]] = {
val la = List.fill(n)(ma)
sequence(la)
}
练习 11.6
实现函数filterM,它看起来和filter类似,它接受的不是函数A=>Bollean,而是A=>F[Boolean]
def filterM[A](la: List[A])(f: A => F[Boolean]): F[List[A]] = {
def loop(n: Int, res: F[List[A]]): F[List[A]] = n match {
case m if m < 0 => res
case _ =>
val temp = flatMap(res){li =>
map(f(la(n))){b =>
if(b) la(n) :: li
else li
}
}
loop(n - 1, temp)
}
loop(la.length - 1, unit(Nil))
}
单子定律
毫无疑问functor法则对Monad是成立的,因为Monad[F]是一个Functor[F],但是除此之外呢?什么样的法则可以约束flatMap和unit?
结合法则
x.flatMap(f).flatMap(g) == x.flatMap(a => f(a).flatMap(g))
KLEISLI组合:结合律更清晰的视图
Monad的结合法则看起来不是很清晰,幸运的是有种方法可以让它更清晰。不考虑F[A]类型monadic值,而是考虑A => F[B]的monadic函数。这样的函数称为Kleisli箭头。它们是可以相互组合的。
练习 11.7
实现Kleisli composition函数compose。
def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C] =
a => flatMap(f(a))(g)
现在可以使用一个更加对称的方式为Monad声明结合法则了:
compose(compose(f, g), h) == compose(f, compose(g, h))
单位元法则
compose(f, unit) == f
compose(unit, f) == f
练习 11.12
第三种monadic组合的最小集合map、unit和join。使用flatMap实现join。
def join[A](mma: F[F[A]]): F[A] =
flatMap(mma){ma => ma}
练习 11.14
使用join和map实现flatMap或compose。
def flatMap[A, B](a: F[A])(f: A => F[B]): F[B] =
join(map(a)(f))
def compose[A, B, C](f: A => F[B], g: B => F[C]): A => F[C] =
a => join(map(f(a))(g))
什么是monadic
Monad和Monoid一样是一个更加抽象、纯代数的接口。Monad组合通常是一个Monad数据类型所有API中的一小部分,Monad不是对一个类型的泛化,而是大量不同的数据类型满足Monad接口和法则的抽象。两个Monad法则需要被满足结合法则(associativity)和单位元法则(identity),它们可以以不同的方式公式化。因此我们可以简单的描述Monad:
monad是一个满足associativity和identity法则的monadic组合的最小集的实现
identity monad
为了提炼monad的本质,先看一个有趣的例子,identity monad。
case class Id[A](value: A)
练习 11.17
为这个类型实现map和flatMap方法,并实现Monod[Id]。
case class Id[A](value: A) {
def map[B](f: A => B): Id[B] = this match {
case Id(a) => Id(f(a))
}
def flatMap[B](f: A => Id[B]) = this match {
case Id(a) => f(a)
}
}
val monadId: Monad[Id] = new Monad[Id] {
override def unit[A](a: => A): Id[A] = Id(a)
override def flatMap[A, B](a: Id[A])(f: (A) => Id[B]): Id[B] =
a.flatMap(f)
}
从上面的中可以看出,monad提供了一个引入和绑定变量的上下文,同时执行了变量替换。
状态monad和partial type application
def stateMonad[S] = new Monad[({type f[X] = State[S, X]}) # f] {
override def unit[A](a: => A): State[S, A] = State(s => (a, s))
override def flatMap[A, B](a: State[S, A])(f: (A) => State[S, B]): State[S, B] =
a.flatMap(f)
}