泛函编程(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分别同时进行函数施用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网大杂烩

Python 异常值分析

异常值分析是检验数据是否有录入错误以及含有不合常理的数据。忽视异常值的存在是十分危险的,不加剔除地把异常值包括进数据的计算分析过程中,对结果会产生不良影响;重视...

912
来自专栏Java呓语

享元模式(分离与共享细粒度对象)

目录: 1、由 HTTP 协议 联想到 对享元模式的思考 2、引入礼盒问题,作为享元模式的逆向思考 3、享元模式的实现 4、享元模式总结 5、感谢帮助...

652
来自专栏数据结构与算法

UOJ #117. 欧拉回路

欧拉回路 有一天一位灵魂画师画了一张图,现在要你找出欧拉回路,即在图中找一个环使得每条边都在环上出现恰好一次。 一共两个子任务: 这张图是无向图。(50分) 这...

3476
来自专栏hanlp学习笔记

Spark应用HanLP对中文语料进行文本挖掘--聚类

用到的知识:HanLP、Spark TF-IDF、Spark kmeans、Spark mapPartition;

400
来自专栏冰霜之地

神奇的德布鲁因序列

数学中存在这样一个序列,它充满魔力,在实际工程中也有一部分的应用。今天就打算分享一下这个序列,它在 Google S2 中是如何使用的以及它在图论中,其他领域中...

1303
来自专栏大数据挖掘DT机器学习

Python爬虫股票评论,snowNLP简单分析股民用户情绪

一、背景 股民是网络用户的一大群体,他们的网络情绪在一定程度上反映了该股票的情况,也反映了股市市场的波动情况。作为一只时间充裕的研究僧,我课余时间准备...

3928
来自专栏数据派THU

教你用Python爬虫股票评论,简单分析股民用户情绪

来源:大数据挖掘DT数据分析 本文长度为1500字,建议阅读7分钟 本文为你分享如何爬取分析股民评论数据,预测用户情绪走势。 一、背景 股民是网络用户的一大群体...

4367
来自专栏hadoop学习笔记

Spark应用HanLP对中文语料进行文本挖掘--聚类详解教程

用到的知识:HanLP、Spark TF-IDF、Spark kmeans、Spark mapPartition;

1040
来自专栏大数据文摘

如何利用维基百科的数据可视化当代音乐史

1747
来自专栏小鹏的专栏

机器学习算法应用中常用技巧-1

参考:Udacity ML纳米学位 1. 取样 数据量很大的时候,想要先选取少量数据来观察一下细节。 indices = [100,200,300] #...

1927

扫码关注云+社区