泛函编程(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 ...
随机推荐
- Atitit 图像处理30大经典算法attilax总结
Atitit 图像处理30大经典算法attilax总结 1. 识别模糊图片算法2 2. 相似度识别算法(ahash,phash,dhash)2 3. 分辨率太小图片2 4. 横条薯条广告2 5. 图像 ...
- 第七节:Class与Style绑定
1.绑定class,v-bind:class 简写为::class 对象方式: <!-- 绑定一个class对象,也可以绑定data中的对象--> <span v-bind:cla ...
- Construction of Primitives in Open Cascade
Construction of Primitives in Open Cascade eryar@163.com 一.创建基本图元 Making Primitives 将用到如下的类创建基本图元,包括 ...
- java.util.Scanner简单应用
import java.util.Scanner; import java.io.*; public class FileScannerTest{ public static void main(St ...
- C#搭建足球赛事资料库与预测平台(1) 基本介绍
本博客所有文章分类的总目录:[总目录]本博客博文总目录-实时更新 开源C#彩票数据资料库系列文章总目录:[目录]C#搭建足球赛事资料库与预测平台与彩票数据分析目录 去年4月到现在,一年 ...
- 使用Expression Tree构建动态LINQ查询
这篇文章介绍一个有意思的话题,也是经常被人问到的:如何构建动态LINQ查询?所谓动态,主要的意思在于查询的条件可以随机组合,动态添加,而不是固定的写法.这个在很多系统开发过程中是非常有用的. 我这里给 ...
- 数据可视化(1)--Chart.js
Chart.js是一个HTML5图表库,使用canvas元素来展示各式各样的客户端图表,支持折线图.柱形图.雷达图.饼图.环形图等.在每种图表中,还包含了大量的自定义选项,包括动画展示形式. Char ...
- c#socket同步通信
再次特别感谢张子阳老师的文章,读后深感益处. 废话不多说,先贴代码 这是服务器端代码 using System; using System.Collections.Generic; using Sys ...
- HTML5移动Web开发(六)——定义一个内容策略
通过分析工具搜集到的数据,你可以定义一个内容策略,这对已经有了一个桌面网站的人是非常有用的. 1.确认你已经把分析工具的Javascript代码嵌入到网站中.(http://www.cnblogs.c ...
- [转载]SVN使用教程
SVN简介: 为什么要使用SVN? 程序员在编写程序的过程中,每个程序员都会生成很多不同的版本,这就需要程序员有效的管理代码,在需要的时候可以迅速,准确取出相应的版本. Subversion是什么? ...