泛函编程(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组件的实现:
- def product[F[_],G[_]](a1: Applicative[F],a2: Applicative[G]) = new Applicative[({type l[V] = (F[V], G[V])})#l] {
- def unit[A](a: A) = (a1.unit(a), a2.unit(a))
- override def apply[A,B](aa: (F[A],G[A]))(fg: (F[A => B], G[A => B])) = {
- (a1.apply(aa._1)(fg._1), a2.apply(aa._2)(fg._2))
- }
- override def map2[A,B,C](fga: (F[A],G[A]), fgb: (F[B],G[B]))(f: (A,B) => C): (F[C],G[C]) = {
- (a1.map2(fga._1,fgb._1)(f), a2.map2(fga._2,fgb._2)(f))
- }
- } //> product: [F[_], G[_]](a1: ch12.ex1.Applicative[F], a2: ch12.ex1.Applicative
- //| [G])ch12.ex1.Applicative[[V](F[V], G[V])]
- def compose[F[_],G[_]](a1: Applicative[F], a2: Applicative[G]) = new Applicative[({type l[V] = F[G[V]]})#l] {
- def unit[A](a: A) = a1.unit(a2.unit(a))
- override def map2[A,B,C](fga: F[G[A]], fgb: F[G[B]])(f: (A,B) => C): F[G[C]] = {
- a1.map2(fga,fgb)((ga,gb) => a2.map2(ga,gb)(f))
- }
- } //> compose: [F[_], G[_]](a1: ch12.ex1.Applicative[F], a2: ch12.ex1.Applicative
- //| [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的函数款式是这样的:
- def traverse[F[_],A,B](as: List[A], f: A => F[B]): F[List[B]]
- def sequence[F[_],A](fas: List[F[A]]): F[List[A]]
我们试试实现Map类型的sequence:
- def sequenceMap[K,V](mfv: Map[K,F[V]]): F[Map[K,V]] = {
- mfv.foldLeft(unit(Map[K,V]())) {
- case (fm,(k,fv)) => map2(fm, map(fv)(v => Map(k -> v)))((m,n) => m ++ n)
- }
- }
实现过程还是挺复杂的。这里有些特别的地方需要注意:在实现Applicative实例时最好实现map2,因为它的函数款式更简单清晰。而在进行Applicative操作时使用apply会更方便。
既然Traversable是那么地普遍,为什么不把它抽象出来形成一个特殊的类型呢?
- trait Traverse[T[_]] {
- def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]]
- def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]]
- }
这个trait里的sequence,traverse函数与我们前面实现的sequence和traverse有什么不同呢?
F[_]变成了AP[_]:Applicative:就是说AP必须是一个Applicative
List变成了T:trait Traverse针对任何T[_],包括List,更概括了。以前的sequence和traverse都只针对List,现在的Traverse类型可以拓展概括所有T[_]这种类型。
我们试着实现这个Traverse类型:
- trait Traverse[T[_]] {
- def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]] = traverse(tapa)(apa =>apa)
- def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]] = sequence(map(ta)(f))
- def map[A,B](ta: T[A])(f: A => B): T[B]
- }
我们可以试着实现List,Option,Tree这几个Traverse类型实例:
- case class Tree[+A](head: A, tail: List[Tree[A]])
- object Traverse {
- val listTraverse = new Traverse[List] {
- override def traverse[AP[_],A,B](la: List[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[List[B]] = {
- la.foldLeft(m.unit(List[B]()))((a,b) => m.map2(f(b),a)(_ :: _))
- }
- }
- val optionTraverse = new Traverse[Option] {
- override def traverse[AP[_],A,B](oa: Option[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[Option[B]] = {
- oa match {
- case Some(a) => m.map(f(a))(b => Some(b))
- case None => m.unit(None)
- }
- }
- }
- val treeTraverse = new Traverse[Tree] {
- override def traverse[AP[_],A,B](ta: Tree[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[Tree[B]] = {
- m.map2(f(ta.head),listTraverse.traverse(ta.tail)(da => traverse(da)(f)))(Tree(_,_))
- }
- }
所有Traverse类型的实例只要实现traverse或sequence就可以了,因为traverse和sequence相互可以实现。
sequence和traverse可以相互实现,但sequence的实现需要使用map。我们可以试着在trait Traverse里实现一个默认的map函数:
我们可以得到一个Identity Functor:type Id[A] = A, 这个东西存粹是为了获取F[A]这么个形状以便匹配类型款式。这样我们可以得出一个Identity Monad:
- type Id[A] = A
- val idMonad = new Monad[Id] {
- def unit[A](a: A) = a
- // override def flatMap[A,B](a: Id[A])(f: A => Id[B]): Id[B] = f(a)
- override def flatMap[A,B](a: A)(f: A => B): B = f(a)
- }
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函数:
- trait Traverse[T[_]] extends Functor[T] {
- def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]] = traverse(tapa)(apa =>apa)
- def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]] = sequence(map(ta)(f))
- def map[A,B](ta: T[A])(f: A => B): T[B] = traverse[Id,A,B](ta)(f)(idMonad)
- type Id[A] = A
- val idMonad = new Monad[Id] {
- def unit[A](a: A) = a
- // override def flatMap[A,B](a: Id[A])(f: A => Id[B]): Id[B] = f(a)
- override def flatMap[A,B](a: A)(f: A => B): B = f(a)
- }
- }
注意:在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操作:
- val listTraverse = new Traverse[List] {
- override def traverse[AP[_],A,B](la: List[A])(f: A => AP[B])(implicit m: Applicative[AP]): AP[List[B]] = {
- la.foldLeft(m.unit(List[B]()))((a,b) => m.map2(f(b),a)(_ :: _))
- }
- }
现在我们通过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实例:
- object Applicative {
- type Const[A, B] = A
- implicit def monoidApplicative[M](m: Monoid[M]) =
- new Applicative[({type alias[x] = Const[M,x]})#alias] {
- def unit[A](a: A): M = m.zero
- override def apply[A,B](m1: M)(m2: M): M = m.op(m1, m2)
- // override def map2[A,B,C](m1: M, m2: M)(f: (A,B) => C): M = m.op(m1, m2)
- }
- }
有了这个Applicative实例,我们就可以在trait Traverse里实现foldMap,也就意味着Traverse可以extend Foldable了:
- trait Traverse[T[_]] extends Functor[T] with Foldable[T] {
- def sequence[AP[_]: Applicative, A](tapa: T[AP[A]]): AP[T[A]] = traverse(tapa)(apa => apa)
- def traverse[AP[_]: Applicative, A, B](ta: T[A])(f: A => AP[B]): AP[T[B]] = sequence(map(ta)(f))
- def map[A, B](ta: T[A])(f: A => B): T[B] = traverse[Id, A, B](ta)(f)(idMonad)
- type Id[A] = A
- val idMonad = new Monad[Id] {
- def unit[A](a: A) = a
- // override def flatMap[A,B](a: Id[A])(f: A => Id[B]): Id[B] = f(a)
- override def flatMap[A, B](a: A)(f: A => B): B = f(a)
- }
- import Applicative._
- override def foldMap[A,B](ta: T[A])(f: A => B)(mb: Monoid[B]): B = {
- traverse[({type alias[x] = Const[B,x]})#alias,A,Nothing](ta)(f)(monoidApplicative(mb))
- }
- }
既然Traverse已经概括了Foldable,而且Traverse类型有比Foldable效率高,那么以后尽量使用Traverse这种类型。
State能够很巧妙地对高阶数据类型结构内部元素进行函数施用同时又维护了运算状态数据。前面我们已经取得了State Nonad实例。因为所有Monad都是Applicative,所以等于我们已经获取了State Applicative Functor实例。如果我们在游览(traverse)一个集合的过程中用State Applicative Functor对集合元素进行操作并且维护状态数据,那么将会实现强大的高阶数据类型处理功能。我们先看一个结合State的traverse函数:
- def traverseS[S,A,B](ta: T[A])(f: A => State[S,B]): State[S,T[B]] = {
- traverse[({type alias[x] = State[S,x]})#alias, A, B](ta)(f)(StateMonad)
- }
我们用这个函数来游览集合并标注行号:
- def zipWithIndex[A](ta: T[A]): T[(A,Int)] = {
- traverseS(ta)(a => for {
- i <- getState
- _ <- setState(i + 1)
- } yield(a,i)
- )).run(0)._1
- }
再用这个函数把T[A]转成List[A]:
- def toList[A](ta: T[A]): List[A] = {
- traverseS(ta)(a => for {
- as <- getState[List[A]]
- _ <- setState(a :: as)
- } yield()
- )).run(List[A]())._2.reverse
- }
这两个利用State的函数语法十分相近。实际上所有State游览(traversal)都很相似。我们可以再进一步抽象:
- def mapS[S,A,B](ta: T[A], s: S)(f: (A,S) => (B,S)): (T[B],S) = {
- traverse(ta)(a => for {
- s1 <- getState
- (b,s2) = f(a,s1)
- _ <- setState(s2)
- } yield(b)
- )).run(s)
- }
- def zipWithIndex[A](ta: T[A]): T[(A,Int)] = {
- mapS(ta,0)((a,s) => ((a,s),s+1))._1
- }
- def toList[A](ta: T[A]): List[A] = {
- mapS(ta,List[A]())((a,s) => ((), a :: s)).reverse
- }
- def reverse[A](ta: T[A]): T[A] = {
- mapS(ta,toList(ta))((_,s) => (s.head, s.tail))._1
- }
- def foldLeft[A,B](ta: T[A])(z: B)(f: (B,A) => B): B = {
- mapS(ta,z)((a,b) => ((),f(b,a)))._2
- }
用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]:
- def zip[A,B](ta: T[A], tb: T[B]): T[(A, B)] =
- (mapS(ta, toList(tb)) {
- case (a, Nil) => sys.error("zip: Incompatible shapes.")
- case (a, b :: bs) => ((a, b), bs)
- })._1
我们可以看到:tb的长度必须等于或大于ta。如此我们只有把这个函数分拆成两种情况的处理函数:
1、ta 长度大于 tb : 用下面的zipL函数
2、tb 长度大于 ta : 用下面的zipR函数
- def zipL[A,B](ta: T[A], tb: T[B]): T[(A, Option[B])] =
- (mapS(ta, toList(tb)) {
- case (a, Nil) => ((a, None), Nil)
- case (a, b :: bs) => ((a, Some(b)), bs)
- })._1
- def zipR[A,B](ta: T[A], tb: T[B]): T[(Option[A], B)] =
- (mapS(tb, toList(ta)) {
- case (b, Nil) => ((None, b), Nil)
- case (b, a :: as) => ((Some(a), b), as)
- })._1
这样我们得出的T[(A,B)]其中T[A],T[B]短出的部分就用None来填补。
我们能像Monoid product 一样在对一个可折叠结构进行游览时对结构内部元素一次性进行多次操作,我们同样可以对可游览结构(Traversable)在一轮游览时对结构内部元素进行多次操作:
- def fuse[M[_],N[_],A,B](ta: T[A])(f: A => M[B], g: A => N[B])
- (implicit m: Applicative[M], n: Applicative[N]): (M[T[B]], N[T[B]]) =
- 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分别同时进行函数施用。
泛函编程(26)-泛函数据类型-Monad-Applicative Functor Traversal的更多相关文章
- 泛函编程(5)-数据结构(Functional Data Structures)
编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...
- 泛函编程(28)-粗俗浅解:Functor, Applicative, Monad
经过了一段时间的泛函编程讨论,始终没能实实在在的明确到底泛函编程有什么区别和特点:我是指在现实编程的情况下所谓的泛函编程到底如何特别.我们已经习惯了传统的行令式编程(imperative progra ...
- 泛函编程(25)-泛函数据类型-Monad-Applicative
上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...
- 泛函编程(24)-泛函数据类型-Monad, monadic programming
在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...
- 泛函编程(23)-泛函数据类型-Monad
简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可 ...
- 备份-泛函编程(23)-泛函数据类型-Monad
泛函编程(23)-泛函数据类型-Monad http://www.cnblogs.com/tiger-xc/p/4461807.html https://blog.csdn.net/samsai100 ...
- 泛函编程(27)-泛函编程模式-Monad Transformer
经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...
- 泛函编程(32)-泛函IO:IO Monad
由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...
- 泛函编程(34)-泛函变量:处理状态转变-ST Monad
泛函编程的核心模式就是函数组合(compositionality).实现函数组合的必要条件之一就是参与组合的各方程序都必须是纯代码的(pure code).所谓纯代码就是程序中的所有表达式都必须是Re ...
随机推荐
- PDO连接mysql和pgsql数据库
PDO连接mysql数据库 <?php $dsn="mysql:host=localhsot;dbname=lamp87"; $user="root"; ...
- 你可能不知道console强大
写在前面 大家都有用过各种类型的浏览器,每种浏览器都有自己的特色,本人拙见,在我用过的浏览器当中,我是最喜欢Chrome的,因为它对于调试脚本及前端设计调试都有它比其它浏览器有过之而无不及的地方.可能 ...
- Masonry -- 使用纯代码进行iOS应用的autolayout自适应布局
简介 简化iOS应用使用纯代码机型自适应布局的工作,使用一种简洁高效的语法替代NSLayoutConstraints. 项目主页: Masonry 最新示例: 点击下载 项目简议: 如果再看到关于纯代 ...
- vue for 绑定事件
vue for 绑定事件 <div id="pro_list" v-for="item in pro_list"> <div class=&q ...
- Java 多态——与C++的比较
学习了Java和C++之后,由于长期不使用C++,而java的基础知识掌握不牢,现在已经搞不清java多态了.现在先来谈谈java多态,稍后有时间再更新C++的多态,并进行比较~ 一. Java的多态 ...
- 初了解JS设计模式,学习笔记
什么是设计模式. 回答这个问题,往往我们得先知道我们为什么需要设计模式,正是因为有需求才会有设计模式,难道不是吗? 我们为什么需要设计模式. 如果没有按照设计模式去写,你的代码很可能是乱无肆忌写的,也 ...
- SQL Pass北京举办第11次线下活动,欢迎报名(本次活动特别邀请了来自微软总部Xin Jin博士)
活动主题: 探讨SQL Server 2014与Fusion IO在SQL Server中的应用 地点:北京微软(中国)有限公司[望京利星行],三层308室 时间:2013年 10 月19日 13:3 ...
- select元素javascript常用操作 转
/*------------------------------------------------------ *作者:xieyu @ 2007-08-14 *语言:JavaScript *说明:s ...
- mongodb-$type、limit、skip、sort方法、索引、聚合
一.$type操作符 $type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果. MongoDB 中可以使用的类型如下表所示: 类型 数字 备注 Double 1 String 2 ...
- Zookeeper-Zookeeper client
当我写完Zookeeper leader选举后,准备看看Zookeeper的存储和处理客户端请求的时候发现,如果能看看Zookeeper的API是不是在理解后面的过程更好些呢. Zookeeper的c ...