泛函编程(21)-泛函数据类型-Monoid
Monoid是数学范畴理论(category theory)中的一个特殊范畴(category)。不过我并没有打算花时间从范畴理论的角度去介绍Monoid,而是希望从一个程序员的角度去分析Monoid以及它在泛函编程里的作用。从这个思路出发我们很自然得出Monoid就是一种数据类型,或者是一种在泛函编程过程中经常会遇到的数据类型:当我们针对List或者loop进行一个数值的积累操作时我们就会使用到Monoid。实际上Monoid就是List[A] => A的抽象模型。好了,我们就不要越描越黑了吧,还是看看Monoid的定义吧:
Monoid由以下条件组成:
1、一个抽象类型A
2、一个二元结合性函数(binary associative function),对传入的两个A类参数进行操作后产生一个A类型结果
3、一个恒等值(identity)
由于Monoid是一个数学类型,它的二元操作函数必须遵循一些定律:
1、结合性(associativity):op(a,op(b,c)) = op(op(a,b),c):这个定律是函数组合(function composition)不可缺的条件
2、二元函数参数中如果有一个是恒等值时操作结果为另一个参数:op(identity,v) = v
我们可以用编程语言来描述Monoid:
trait Monoid[A] { //被封装的类型A
def op(a1: A, a2: A): A //二元函数
val zero: A //恒等值identity
}
我们用scala的特质(trait)描述了Monoid。它就是一个抽象的数据类型。
既然Monoid trait是个抽象类型,那么我们可以试着创建几个基础类型的Monoid实例:
val stringConcatMonoid = new Monoid[String] {
def op(s1: String, s2: String) = s1 + s2
val zero = "" // op(zero,s2) = "" + s2 = s2 恒等值定律
} //> stringConcatMonoid : ch10.ex1.Monoid[String] = ch10.ex1$$anonfun$main$1$$an
//| on$1@3581c5f3
val intAdditionMonoid = new Monoid[Int] {
def op(i1: Int, i2: Int) = i1 + i2
val zero = 0
} //> intAdditionMonoid : ch10.ex1.Monoid[Int] = ch10.ex1$$anonfun$main$1$$anon$4
//| @340f438e
val intMultiplicationMonoid = new Monoid[Int] {
def op(i1: Int, i2: Int) = i1 * i2
val zero = 1
} //> intMultiplicationMonoid : ch10.ex1.Monoid[Int] = ch10.ex1$$anonfun$main$1$$
//| anon$5@30c7da1e
可以看出,这几个Monoid实例都符合Monoid定律。那我们可以先试着用用。上面提到Monoid最适合一串值的累加操作List[A] => A,我们可以对List[A]进行操作示范:
def reduce[A](as: List[A])(m: Monoid[A]): A = {
as match {
case Nil => m.zero
case h::t => m.op(h, reduce(t)(m))
}
} //> reduce: [A](as: List[A])(m: ch10.ex1.Monoid[A])A
Monoid m是个抽象类型,m.zero和m.op()的具体意义要看Monoid的实例了:
reduce(List(1,2,3))(intAdditionMonoid) //> res3: Int = 6
reduce(List("this is ","the string", " monoid"))(stringConcatMonoid)
//> res4: String = this is the string monoid
对List[A]的具体累加处理是按照intAdditionMonoid和stringConcatMonoid的二元函数功能进行的。看来Monoid特别适用于List类型的循环操作。可以把reduce函数的参数拓展开来看看:
reduce[A](as: List[A])(zero: A)(op: (A,A) => A) : A
这个类型款式跟折叠算法的类型款式非常相似:
def foldRight[A,B](as: List[A])(z: B)(f: (A,B) => B): B
如果类型B=类型A
def foldRight[A](as: List[A])(z: A)(f: (A,A) => A): A
实际上我们可以直接用上面的Monoid实例运算折叠算法:
List(1,2,3).foldRight(intAdditionMonoid.zero)(intAdditionMonoid.op)
//> res3: Int = 6
List("this is ","the string", " monoid").foldLeft(stringConcatMonoid.zero)(stringConcatMonoid.op)
//> res4: String = this is the string monoid
左右折叠算法都可以。Monoid的结合性定律(associativity law)可以使List元素运算左右路径相等。
下面我们再试着增加几个Monoid实例:
def optionMonoid[A] = new Monoid[Option[A]] {
def op(o1: Option[A], o2: Option[A]): Option[A] = o1 orElse o2
val zero = None // op(zero, o1)= None orElse o2 = o2
} //> optionMonoid: [A]=> ch10.ex1.Monoid[Option[A]]{val zero: None.type}
def listConcatMonoid[A] = new Monoid[List[A]] {
def op(l1: List[A], l2: List[A]) = l1 ++ l2
val zero = Nil
} //> listConcatMonoid: [A]=> ch10.ex1.Monoid[List[A]]{val zero: scala.collection.
//| immutable.Nil.type}
val booleanOrMonoid = new Monoid[Boolean] {
def op(b1: Boolean, b2: Boolean) = b1 || b2
val zero = false
} //> booleanOrMonoid : ch10.ex1.Monoid[Boolean] = ch10.ex1$$anonfun$main$1$$anon
//| $6@5b464ce8
val booleanAndMonoid = new Monoid[Boolean] {
def op(b1: Boolean, b2: Boolean) = b1 && b2
val zero = true
} //> booleanAndMonoid : ch10.ex1.Monoid[Boolean] = ch10.ex1$$anonfun$main$1$$an
//| on$7@57829d67
def endoComposeMonoid[A] = new Monoid[A => A] {
def op(f: A => A, g: A => A) = f compose g
val zero = (a: A) => a // op(zero, g: A => A) = zero compose g = g
} //> endoComposeMonoid: [A]=> ch10.ex1.Monoid[A => A]
def endoAndThenMonoid[A] = new Monoid[A => A] {
def op(f: A => A, g: A => A) = f andThen g
val zero = (a: A) => a // op(zero, g: A => A) = zero andThen g = g
} //> endoAndThenMonoid: [A]=> ch10.ex1.Monoid[A => A]
//计算m的镜像Monoid
def dual[A](m: Monoid[A]) = new Monoid[A] {
def op(x: A, y: A) = m.op(y,x) //镜像op即时二元参数位置互换
val zero = m.zero
} //> dual: [A](m: ch10.ex1.Monoid[A])ch10.ex1.Monoid[A]
def firstOfDualOptionMonoid[A] = optionMonoid[A]
//> firstOfDualOptionMonoid: [A]=> ch10.ex1.Monoid[Option[A]]{val zero: None.ty
//| pe}
def secondOfDualOptionMonoid[A] = dual(firstOfDualOptionMonoid[A])
//> secondOfDualOptionMonoid: [A]=> ch10.ex1.Monoid[Option[A]]
以上几个增加的Monoid实例中endoComposeMonoid和endoAndThenMonoid可能比较陌生。它们是针对函数组合的Monoid。
还是回到对List[A]的累加操作。下面这个函数用Monoid对List[A]元素A进行累加操作:
def concatenate[A](l: List[A], m: Monoid[A]): A = {
l.foldRight(m.zero){(a,b) => m.op(a,b)}
} //> concatenate: [A](l: List[A], m: ch10.ex1.Monoid[A])A
concatenate[Int](List(1,2,3),intAdditionMonoid) //> res0: Int = 6
那么如果没有List[A]元素A类型Monoid实例怎么办?我们可以加一个函数:
def foldMap[A,B](as: List[A])(m: Monoid[B])(f: A => B): B
如果我们有一个函数可以把A类转成B类 A => B,那我们就可以使用Monoid[B]了:
def foldMap[A,B](as: List[A])(m: Monoid[B])(f: A => B): B = {
as.foldRight(m.zero)((a,b) => m.op(f(a),b))
}
说明一下:foldRight的类型款式:foldRight[A,B](as: List[A])(z: B)(g: (A,B) => B): B。其中(A,B) => B >>> (f(A),B) => B >>> (B,B) => B 就可以使用 Monoid[B].op(B,B)=B了。我们也可以用foldLeft来实现foldMap。实际上我们同样可以用foldMap来实现foldRight和foldLeft:
def foldRight[A,B](la: List[A])(z: B)(f: (A,B) => B): B
def foldLeft[A,B](la: List[A])(z: B)(f: (A,B) => B): B
def foldMap[A,B](as: List[A])(m: Monoid[B])(f: A => B): B
foldRight和foldLeft的f函数是(A,B) => B,如果用curry表达:A => (B => B),如果能把 A => ? 转成 B => B,那么我们就可以使用endoComposeMonoid[B].op(f: B => B, g: B => B): B。
def foldRight[A,B](as: List[A])(z: B)(f: (A,B) => B): B = {
foldMap(as)(endoComposeMonoid[B])(a => b => f(a,b))(z)
}
说明:foldMap需要f: A => B, foldRight有 (A,B) => B >>> A => B => B >>> f(a)(b) => b >>> f(a,b)(z) >>> f(b)(b)
foldLeft是从左边开始折叠,只需要采用endoComposeMonoid的镜像Monoid把op参数位置调换就行了:
def foldLeft[A,B](as: List[A])(z: B)(f: (A,B) => B): B = {
foldMap(as)(dual(endoComposeMonoid[B]))(a => b => f(a,b))(z)
}
在这节我们简单的介绍了Monoid及它的一些初级类型的实例使用方式。我们也把Monoid代数模型的一面:函数的互通转换及组合稍微示范了一下。在下一节我们将会把Monoid在实际编程中的应用以及Monoid的深度抽象做些讨论。
泛函编程(21)-泛函数据类型-Monoid的更多相关文章
- 泛函编程(5)-数据结构(Functional Data Structures)
编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...
- 泛函编程(24)-泛函数据类型-Monad, monadic programming
在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...
- 泛函编程(23)-泛函数据类型-Monad
简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可 ...
- 泛函编程(25)-泛函数据类型-Monad-Applicative
上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...
- 备份-泛函编程(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, ...
- 泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink
上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个Process的输入端连接 ...
- 泛函编程(34)-泛函变量:处理状态转变-ST Monad
泛函编程的核心模式就是函数组合(compositionality).实现函数组合的必要条件之一就是参与组合的各方程序都必须是纯代码的(pure code).所谓纯代码就是程序中的所有表达式都必须是Re ...
- 泛函编程(32)-泛函IO:IO Monad
由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...
随机推荐
- Silverlight:版本控制的衍化
版本控制是企业开发中一个老生长谈的主题,这也是大部分公司新人进来后需要接纳的一个基础知识体系. 从08年首次接触商业软件编写后,这几年先后接触了SVN,TFS,Git这几个主要的版本控制器,但是并没有 ...
- Filter之——GZIP全站压缩
GZIP压缩:将压缩后的文本文件,发送给浏览器,减少流量. 一.进行gzip压缩条件: 1.请求头:Accept-Encoding : gzip 告诉服务器,该浏览器支持gzip压缩. 2.响应头: ...
- SAP ECC MM 配置文档
SAP ECC 6.0 Configuration Document Materials Management (MM) Table of Content TOC \o \h \z 1. Genera ...
- saiku 分布式实践
saiku比较吃内存,一旦人多了,那么内存可能不够,所以会考虑主从结构,分担压力.为了保证数据的稳定性,也会有类似的考虑,那么问题来了,如何实现saiku的分布式搭建哪? 我阅读了一些国内的文章,没有 ...
- 深入浅出 nginx lua 为什么高性能
最近很多人问我nginx lua的优势是什么?为什么? 一.同步和异步.阻塞和非阻塞 如果要说清楚这个问题首先要了解:同步和异步.阻塞和非阻塞的关系 同步:php.java的正常代码都是同步执行的 异 ...
- SQL Server2008如何设置开启远程连接
登陆SQL Server 2008(windows身份认证),登陆后右击,选择“属性”. 2 左侧选择“安全性”,选中右侧的“SQL Server 和 Windows 身份验证模式”以启用混合登录模式 ...
- java之容器
先来一张容器的API框架图,我们在java中所学的所有知识,都是根据下面这张图来学习的.... 容器API: 1.Collection接口------定义了存储一组对象的方法,其子接口Set和List ...
- 突破GFW,使用node.js
原文链接:https://cnodejs.org/topic/4f9904f9407edba21468f31e 这个也是网上搜的,亲自试过,非常好用! 镜像使用方法(三种办法任意一种都能解决问题,建议 ...
- 条件注释判断浏览器<!--[if !IE]><!--[if IE]><!--[if lt IE 6]><!--[if gte IE 6]>
<!--[if !IE]><!--> 除IE外都可识别 <!--<![endif]--><!--[if IE]> 所有的IE可识别 <![e ...
- [转]非OpenVZ下利用谷歌TCP-BBR协议单边加速你的VPS
前段时间谷歌推出了新的 TCP-BBR 开源算法,可以起到单边加速 TCP 连接的效果,也就是不用客户端的配合,用来替代收费的锐速再合适不过,毕竟开源免费.TCP-BBR 的目的是要尽量跑满带宽,并且 ...