泛函编程(26)-泛函数据类型-Monad-Applicative Functor Traversal

简介:

  前面我们讨论了Applicative。Applicative 就是某种Functor,因为我们可以用map2来实现map,所以Applicative可以map,就是Functor,叫做Applicative Functor。我们又说所有Monad都是Applicative,因为我们可以用flatMap来实现map2,但不是所有数据类型的flatMap都可以用map2实现,所以反之不是所有Applicative都是Monad。Applicative注重于各种类型的函数施用,也就是map。包括普通函数的施用及在高阶类型结构内的函数施用,还有多参数函数的连续施用。表面上看来Monad已经覆盖了Functor, Applicative。可能就是因为Monad的功能太强大了,所以Monad的函数组合(functional composition)非常有限。我们只能从Applicative来获取函数组合这部分的能力了。

    我们先研究一下Applicative的一些规则:

1、map的恒等定律:map(v)(x => x) === v

   map(v)(f) === apply(unit(f))(v), 就是说把一个函数f用unit升阶后apply就是map。apply就是map的一种。

   Applicative的恒等定律:apply(unit(x => x))(v) === v

2、Applicative的函数组合:如果我们具备以下条件:

   f类型 = F[A => B], g类型 = F[B => C],x类型 = F[A]。那么:

   apply(map2(f,g)(_ compose _))(x) === apply(f)(apply(g)(x)),或者更直接一点:

   map3(f,g,x)(_(_(_))) === f(g(x))。map3就是函数升阶组合:把函数 (_(_(_))) >>> (a,b,c) => d 在高阶类型结构内进行组合。

先看看两个Applicative组件的实现:


 1   def product[F[_],G[_]](a1: Applicative[F],a2: Applicative[G]) = new Applicative[({type l[V] = (F[V], G[V])})#l] {
 2       def unit[A](a: A) = (a1.unit(a), a2.unit(a))
 3       override def apply[A,B](aa: (F[A],G[A]))(fg: (F[A => B], G[A => B])) = {
 4           (a1.apply(aa._1)(fg._1), a2.apply(aa._2)(fg._2))
 5       }
 6       override def map2[A,B,C](fga: (F[A],G[A]), fgb: (F[B],G[B]))(f: (A,B) => C): (F[C],G[C]) = {
 7           (a1.map2(fga._1,fgb._1)(f), a2.map2(fga._2,fgb._2)(f))
 8       }
 9   }                                               //> product: [F[_], G[_]](a1: ch12.ex1.Applicative[F], a2: ch12.ex1.Applicative
10                                                   //| [G])ch12.ex1.Applicative[[V](F[V], G[V])]
11   def compose[F[_],G[_]](a1: Applicative[F], a2: Applicative[G]) = new Applicative[({type l[V] = F[G[V]]})#l] {
12       def unit[A](a: A) = a1.unit(a2.unit(a))
13       override def map2[A,B,C](fga: F[G[A]], fgb: F[G[B]])(f: (A,B) => C): F[G[C]] = {
14           a1.map2(fga,fgb)((ga,gb) => a2.map2(ga,gb)(f))
15       }
16   }                                               //> compose: [F[_], G[_]](a1: ch12.ex1.Applicative[F], a2: ch12.ex1.Applicative
17                                                   //| [G])ch12.ex1.Applicative[[V]F[G[V]]]

对于Applicative F,G来说 (F[A],G[A]), F[G[A]]也都是Applicative。通过对两个Applicative进行函数组合后形成一个更高阶的Applicative。这个Applicative和其它普通的Applicative一样可以实现多参数函数的升阶连续施用。

Applicative另外一个强项体现在针对可游览类型(traversable type)内部元素的函数施用(map)。与可折叠类型(foldable type)相比较,traversable类型更加抽象,能覆盖类型更多数据类型。Foldable类型的map用Monoid实现,但Monoid无法完全实现Traversable类型的map。Traversable类型的map是通过Applicative实现。Traversable覆盖了Foldable,所以Foldable只是Traversable的其中一种案例。那么我们就看看这个Traversable类型:

在前面我们讨论过的数据类型里,我们都会实现traverse,sequence这两个函数。那是因为我们尝试把那些数据类型都变成Traversable类型。traverse,sequence的函数款式是这样的:


1 def traverse[F[_],A,B](as: List[A], f: A => F[B]): F[List[B]]
2 def sequence[F[_],A](fas: List[F[A]]): F[List[A]]

我们试试实现Map类型的sequence:


1     def sequenceMap[K,V](mfv: Map[K,F[V]]): F[Map[K,V]] = {
2         mfv.foldLeft(unit(Map[K,V]())) {
3             case (fm,(k,fv)) => map2(fm, map(fv)(v => Map(k -> v)))((m,n) => m ++ n)
4         }
5     }

实现过程还是挺复杂的。这里有些特别的地方需要注意:在实现Applicative实例时最好实现map2,因为它的函数款式更简单清晰。而在进行Applicative操作时使用apply会更方便。

既然Traversable是那么地普遍,为什么不把它抽象出来形成一个特殊的类型呢?


1 trait Traverse[T[_]] {
2       def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]] 
3       def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]]
4   }

这个trait里的sequence,traverse函数与我们前面实现的sequence和traverse有什么不同呢?

F[_]变成了AP[_]:Applicative:就是说AP必须是一个Applicative

List变成了T:trait Traverse针对任何T[_],包括List,更概括了。以前的sequence和traverse都只针对List,现在的Traverse类型可以拓展概括所有T[_]这种类型。

我们试着实现这个Traverse类型:


1  trait Traverse[T[_]] {
2       def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]] = traverse(tapa)(apa =>apa)
3       def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]] = sequence(map(ta)(f))
4       def map[A,B](ta: T[A])(f: A => B): T[B]
5   }

我们可以试着实现List,Option,Tree这几个Traverse类型实例:


 1  case class Tree[+A](head: A, tail: List[Tree[A]])
 2   object Traverse {
 3       val listTraverse = new Traverse[List] {
 4           override def traverse[AP[_],A,B](la: List[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[List[B]] = {
 5             la.foldLeft(m.unit(List[B]()))((a,b) => m.map2(f(b),a)(_ :: _))
 6           }
 7       }
 8       val optionTraverse = new Traverse[Option] {
 9           override def traverse[AP[_],A,B](oa: Option[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[Option[B]] = {
10               oa match {
11                   case Some(a) => m.map(f(a))(b => Some(b))
12                   case None => m.unit(None)
13               }
14           }
15       }     
16       val treeTraverse = new Traverse[Tree] {
17           override def traverse[AP[_],A,B](ta: Tree[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[Tree[B]] = {
18             m.map2(f(ta.head),listTraverse.traverse(ta.tail)(da => traverse(da)(f)))(Tree(_,_))
19           }
20       }

所有Traverse类型的实例只要实现traverse或sequence就可以了,因为traverse和sequence相互可以实现。

sequence和traverse可以相互实现,但sequence的实现需要使用map。我们可以试着在trait Traverse里实现一个默认的map函数:

我们可以得到一个Identity Functor:type Id[A] = A, 这个东西存粹是为了获取F[A]这么个形状以便匹配类型款式。这样我们可以得出一个Identity Monad:


1      type Id[A] = A
2       val idMonad = new Monad[Id] {
3          def unit[A](a: A) = a  
4  //         override def flatMap[A,B](a: Id[A])(f: A => Id[B]): Id[B] = f(a)
5            override def flatMap[A,B](a: A)(f: A => B): B = f(a)
6       }

raverse会保留Traverse类型的原始结构。这点从Traverse定律可以推导:针对Traverse[F], xs类型是F[A]的话:

 traverse[Id,A,A](xs)(x => x) === xs >>> map(xs)(x => x) ===xs, 这不就是Functor恒等定律吗?也就是说把traverse需要的Applicative Functor降至Id Functor后traverse相当于map操作。换句话说Traverse可以概括Functor并且traverse操作要比map操作强大许多。

这样我们用idMonad就可以实现一个默认的map函数:


1   trait Traverse[T[_]] extends Functor[T] {
 2       def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]] = traverse(tapa)(apa =>apa)
 3       def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]] = sequence(map(ta)(f))
 4       def map[A,B](ta: T[A])(f: A => B): T[B] = traverse[Id,A,B](ta)(f)(idMonad)
 5       
 6       type Id[A] = A
 7       val idMonad = new Monad[Id] {
 8         def unit[A](a: A) = a
 9  //         override def flatMap[A,B](a: Id[A])(f: A => Id[B]): Id[B] = f(a)
10           override def flatMap[A,B](a: A)(f: A => B): B = f(a)
11       }
12   }

注意:在scala语法中:

 def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]]

AP[_]:Applicative是context bound, 相当于:

def traverse[AP[_], A, B](ta: T[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[T[B]]

所以:

 def map[A,B](ta: T[A])(f: A => B): T[B] = traverse[Id,A,B](ta)(f)(idMonad)

为什么需要这个context bound Applicative实例?实现traverse时需要Applicative的map,map2操作:


1       val listTraverse = new Traverse[List] {
2         override def traverse[AP[_],A,B](la: List[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[List[B]] = {
3             la.foldLeft(m.unit(List[B]()))((a,b) => m.map2(f(b),a)(_ :: _))
4         }
5       }

现在我们通过Traverse类型可以实现游览(Traversal)那么Traverse和Foldable有什么区别吗?从表面上来看Traverse应该比Foldable更高效,因为Foldable是通过Monoid来对结构内的元素进行函数施用的,而Applicative比Monoid更强大。我们先看看Foldable最概括的操作函数foldMap:

def foldMap[A,B](as: F[A])(f: A => B)(mb: Monoid[B]): B

再看看traverse的函数款式:

 def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]]

如果我们把AP[A]换成一个特制的类型:type ConstInt[A] = Int, 经过替换traverse就变成了:

 def traverse[A,B](fa: T[A])(f: A => Int): Int

经过替换的traverse在函数款式上很像foldMap。那么我们就制造一个对任何B的类型:

 type Const[A,B] = A

 

用这个类型加上Monoid实现一个Applicative实例:


1   object Applicative {
2     type Const[A, B] = A
3         implicit def monoidApplicative[M](m: Monoid[M]) = 
4           new Applicative[({type alias[x] = Const[M,x]})#alias] {
5             def unit[A](a: A): M = m.zero
6             override def apply[A,B](m1: M)(m2: M): M = m.op(m1, m2)
7 //            override def map2[A,B,C](m1: M, m2: M)(f: (A,B) => C): M = m.op(m1, m2)
8         }
9   }

有了这个Applicative实例,我们就可以在trait Traverse里实现foldMap,也就意味着Traverse可以extend Foldable了:


 1    trait Traverse[T[_]] extends Functor[T] with Foldable[T] {
 2     def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]] = traverse(tapa)(apa => apa)
 3     def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]] = sequence(map(ta)(f))
 4     def map[A, B](ta: T[A])(f: A => B): T[B] = traverse[Id, A, B](ta)(f)(idMonad)
 5 
 6     type Id[A] = A
 7     val idMonad = new Monad[Id] {
 8       def unit[A](a: A) = a
 9       //         override def flatMap[A,B](a: Id[A])(f: A => Id[B]): Id[B] = f(a)
10       override def flatMap[A, B](a: A)(f: A => B): B = f(a)
11     }
12     import Applicative._
13     override def foldMap[A,B](ta: T[A])(f: A => B)(mb: Monoid[B]): B = {
14         traverse[({type alias[x] = Const[B,x]})#alias,A,Nothing](ta)(f)(monoidApplicative(mb))
15     }  
16   }

既然Traverse已经概括了Foldable,而且Traverse类型有比Foldable效率高,那么以后尽量使用Traverse这种类型。

 State能够很巧妙地对高阶数据类型结构内部元素进行函数施用同时又维护了运算状态数据。前面我们已经取得了State Nonad实例。因为所有Monad都是Applicative,所以等于我们已经获取了State Applicative Functor实例。如果我们在游览(traverse)一个集合的过程中用State Applicative Functor对集合元素进行操作并且维护状态数据,那么将会实现强大的高阶数据类型处理功能。我们先看一个结合State的traverse函数:


1     def traverseS[S,A,B](ta: T[A])(f: A => State[S,B]): State[S,T[B]] = {
2         traverse[({type alias[x] = State[S,x]})#alias, A, B](ta)(f)(StateMonad)
3     }

我们用这个函数来游览集合并标注行号:


1   def zipWithIndex[A](ta: T[A]): T[(A,Int)] = {
2         traverseS(ta)(a => for {
3           i <- getState
4           _ <- setState(i + 1)
5         } yield(a,i)
6         )).run(0)._1
7     }

再用这个函数把T[A]转成List[A]:


1     def toList[A](ta: T[A]): List[A] = {
2         traverseS(ta)(a => for {
3           as <- getState[List[A]]
4           _ <- setState(a :: as) 
5         } yield()
6         )).run(List[A]())._2.reverse
7     } 

这两个利用State的函数语法十分相近。实际上所有State游览(traversal)都很相似。我们可以再进一步抽象:


1    def mapS[S,A,B](ta: T[A], s: S)(f: (A,S) => (B,S)): (T[B],S) = {
 2         traverse(ta)(a => for {
 3           s1 <- getState
 4           (b,s2) = f(a,s1)
 5           _ <- setState(s2)
 6         } yield(b)
 7         )).run(s)
 8     }
 9     def zipWithIndex[A](ta: T[A]): T[(A,Int)] = {
10         mapS(ta,0)((a,s) => ((a,s),s+1))._1
11     }
12     def toList[A](ta: T[A]): List[A] = {
13         mapS(ta,List[A]())((a,s) => ((), a :: s)).reverse
14     }
15     def reverse[A](ta: T[A]): T[A] = {
16         mapS(ta,toList(ta))((_,s) => (s.head, s.tail))._1
17     }
18     def foldLeft[A,B](ta: T[A])(z: B)(f: (B,A) => B): B = {
19         mapS(ta,z)((a,b) => ((),f(b,a)))._2
20     }

用mapS重新实现zipWithIndex,toList,foldLeft就简单的多。mapS游览天生是反序的,所以reverse函数只要对T[A]用mapS走一次就行了。

我们发现Traverse类型会保持它的结构,这是它的强项也是它的弱点。如果我们尝试将两个Traverse结构T[A],T[B]拼接成T[(A,B}]时就会发现这个操作对T[A]和T[B]的长度是有一定要求的。我们先试着用mapS来拼接T[A],T[B]:


1    def zip[A,B](ta: T[A], tb: T[B]): T[(A, B)] =
2     (mapS(ta, toList(tb)) {
3       case (a, Nil) => sys.error("zip: Incompatible shapes.")
4       case (a, b :: bs) => ((a, b), bs)
5     })._1

我们可以看到:tb的长度必须等于或大于ta。如此我们只有把这个函数分拆成两种情况的处理函数:

1、ta 长度大于 tb : 用下面的zipL函数

2、tb 长度大于 ta : 用下面的zipR函数


 1   def zipL[A,B](ta: T[A], tb: T[B]): T[(A, Option[B])] =
 2     (mapS(ta, toList(tb)) {
 3       case (a, Nil) => ((a, None), Nil)
 4       case (a, b :: bs) => ((a, Some(b)), bs)
 5     })._1
 6 
 7   def zipR[A,B](ta: T[A], tb: T[B]): T[(Option[A], B)] =
 8     (mapS(tb, toList(ta)) {
 9       case (b, Nil) => ((None, b), Nil)
10       case (b, a :: as) => ((Some(a), b), as)
11     })._1

这样我们得出的T[(A,B)]其中T[A],T[B]短出的部分就用None来填补。

我们能像Monoid product 一样在对一个可折叠结构进行游览时对结构内部元素一次性进行多次操作,我们同样可以对可游览结构(Traversable)在一轮游览时对结构内部元素进行多次操作:


1   def fuse[M[_],N[_],A,B](ta: T[A])(f: A => M[B], g: A => N[B])
2                          (implicit m: Applicative[M], n: Applicative[N]): (M[T[B]], N[T[B]]) =
3     traverse[({type f[x] = (M[x], N[x])})#f, A, B](ta)(a => (f(a), g(a)))(product(m, n))

两个Applicative实例通过product函数变成Applicative[(M,N)]实例。在traverse运行中m,n分别同时进行函数施用。


相关文章
|
7月前
|
Linux
Linux多线程基础函数使用
Linux多线程基础函数使用
67 0
|
Rust
15 分钟了解 Monad(上)
15 分钟了解 Monad
86 0
|
设计模式 存储 数据库
15 分钟了解 Monad(下)
15 分钟了解 Monad
91 0
|
算法 搜索推荐 C++
第九层(10):STL之函数对象
第九层(10):STL之函数对象
第九层(10):STL之函数对象
|
存储 程序员 C++
【C++ Primer】第9章:顺序容器
【C++ Primer】第9章:顺序容器
157 0
【C++ Primer】第9章:顺序容器
|
JavaScript 容器
【函数式编程】基于JS进行函数式编程(四)函子 | MayBe函子 | Monad函子
【函数式编程】基于JS进行函数式编程(四)函子 | MayBe函子 | Monad函子
177 0
【函数式编程】基于JS进行函数式编程(四)函子 | MayBe函子 | Monad函子