【函数式】Monads模式初探——Monoids

Monads是什么

知乎里有关于什么是Monad的问题讨论,而在维基百科中也有关于Monad的释义。作为初次接触到Monads概念,难免会有些晕头转向,也难免会有些畏惧(因为Monads和数学中的范畴论有密切关系),但是Monads又是如此的重要,因为它在函数式编程中实在是应用太广泛了,并且在Scala的标准库中又常常遇到,使得我们不得不好好研究一番。

Monoids

Monoids是一种元素的集合,它需要满足结合律和幺元(Identity,也称为单位元,这种元和其他元素结合时,不会改变那么元素)这些约束条件。
比如:

  • 整数类型Int,其中0是Identity,其中的任何整数满足结合律
  • 列表类型List,任何两个列表可以通过:::连接起来,其中Nil或空列表[]是Identity
  • 字符串类型String,两个字符串可以拼接,其中空字符串或""是Identity

Monoids在平常的编程之中无处不在,当用到一个列表,连接字符串,通过一个循环得到一个累加结果,都在使用到Monoids。

条件和定律

  1. 一个抽象类型A
  1. 一个二元结合性函数(binary associative function),对传入的两个A类参数进行操作后产生一个A类型结果。op操作必须是结合性的,即op(x, y) == op(y, x);op(a,op(b,c)) = op(op(a,b),c):这个定律是函数组合(function composition)不可缺的条件
  2. 一个恒等值(identity)。二元函数参数中如果有一个是恒等值时操作结果为另一个参数,即满足op(identity, x) == x

示例

Monoid可以用下面的代码描述:

trait Monoid[T] {
  def op(m1: T, m2: T): T
  val identity: T
}

这个特质可以被混入类型(classes)、对象(objects)或者其他特质中。请看下面的举例:

case class StringMonoid extends Monoid[String] {
  def op(s1: String, s2: String) = s1 + s2
  val identity = ""
}

val stringMonoid = StringMonoid()
println(stringMonoid.op(stringMonoid.identity, "John"))
println(stringMonoid.op("John", "Hunt"))
// Output is
// John
// JohnHunt

object IntMonoid extends Monoid[Int] {
  def op(x: Int, y: Int) = x + y
  val identity = 0
}

println(IntMonoid.op(IntMonoid.identity, 1))
println(IntMonoid.op(1, 2))
println(IntMonoid.op(2, 1))
// Output is
// 1
// 3
// 3

Monoid和折叠

如果有一个Monoid结构和一组数据。可以通过对每个元素进行Monoid的op操作来将集合缩减为一个值,比如将一个整数列表通过元素累加的方式得到所有整数的和。
Monoid和List有着密切的联系。在List的foldLeft操作中,用一个初始元素从列表的左边元素开始操作,一直到对所有元素都操作完。如List("A", "B", "C").foldLeft("")(_ + _)这个对字符串列表实现累加功能,foldLeft传入的两个参数分别是空字符串和二元操作运算,这正好符合Monoid的定义,可以轻松利用StringMonoid代替,List("A", "B", "C").foldLeft(StringMonoid.identity)(StringMonoid.op)

结合性与并行化

Monoid的结合性意味着我们在对类似List的数据结构进行折叠的时候有很大的灵活性。我们已经知道可以使用foldLeft和foldRight对一个列表进行顺序的规则(reduce)操作。但是我们同样可以将数据分成多份,并行的进行折叠,然后利用monoid将各个部分合并起来。
左折叠操作是op(op(op(a, b), c), d)
右折叠操作是op(a, op(b, op(c, d)))
并行算法为op(op(a, b), op(c, d)),其中op(a, b)和op(c, d)是同时运算的。
如果我们对一个超大文件进行文字数统计或者寻找最大值什么的,我们可以把这个大文件分成若干小文件然后同时计算后再合计将节省很多计算时间。

Monoid模式的优缺点

优点:

  • Monoid模式提供了一种在特定场景下将元素合并的标准方法
  • 结合性的保证可以用来定义函数之间组合

缺点:

  • 并不是所有集合都可以很容易的应用Monoid模式。比如String Monoid,不同顺序的字符串进行连接可能会得到不同的结果。

从范畴论到计算机编程

从Monoid到Monad,这些概念都是从范畴论中衍生出来的。
理解范畴论的一个好方法是把它理解为应用到函数式编程领域的设计模式。范畴论定义了一些非常底层的概念抽象,这些概念可以直接用Scala这样的支持函数式编程的语言表达。在设计软件的时候,如果一个特定实体符合其中一个概念,那么立刻就有一整组操作可用,而且包含推理其用法的方法。

范畴是由元素对象和态射箭头组成的,这个箭头开始端是一个元素对象,目的地也是一个元素对象。这里态射箭头有两种,不同元素对象比如a和b之间的态射箭头称为组合箭头,而指向自己的箭头称为元箭头,或者单元,幺元。

范畴的元素对象和箭头态射的规则如下:

  1. 对于箭头f:a -> b和箭头g:b -> c,如果有一个箭头h: a -> c,那么就称为它们的组合,写法是:h=g·f
  1. 对于每个元素对象,都有一个单元箭头:id:a -> a。对于任何f: a -> b,满足f·id = f;对于任何g: c -> a,满足id·g = g
  2. 组合符合结合律:f·(g·h) = (f·g)·h
    态射箭头有两种,一种是标号1的组合箭头,还有一种是标号2的单元箭头。

我们将一个范畴有元素对象和态射箭头,态射箭头有组合和幺元两种,且满足结合律,这种范畴称为Monoid。

对于某非空集合S,若存在S上的二元运算"*"使得对于任意的a,b∈S,有a*b∈S(运算封闭),则称{S,*}为广群。 广群只是定义一个集合,集合中有元素和操作,操作结果也属于这个集合,这样泛泛的集合称为广群。 如果广群再加上结合律约束,就会得到半群,因此半群是广群的子集,要求更苛刻些,而半群如果再加上幺元(identity element)就是幺半群,也就是结合律+幺元=幺半群,所以,Monid对应的中文是幺半群。

参考资料

Functional Programming in Scala
Scala Design Patterns: Patterns for Practical Reuse and Design
什么是Monoid?
我所理解的monad(2):fold与monoid

转载请注明作者Jason Ding及其出处
Github博客主页(http://jasonding1354.github.io/)
GitCafe博客主页(http://jasonding1354.gitcafe.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354进入我的博客主页

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 单子 单子(Monad)是一种将函子组合应用的方法。在计算机科学里,单子经常用来代表计算(computation)...
    JasonDing阅读 1,632评论 0 2
  • 背景 所有一切的开始都是因为这句话:一个单子(Monad)说白了不过就是自函子范畴上的一个幺半群而已,有什么难以理...
    福克斯记阅读 13,794评论 6 65
  • 看电影,甚至于看同一部电影,对不同人而言也是具有双重属性的。对绝大多数人而言,它是“娱乐的”甚至“乏味的”,而对一...
    江寒园阅读 8,564评论 63 327
  • 成绩出来的时候总是我 沮丧的时刻 回过头来看过去几年之中,和应试各种考试作战,这样的情绪一直贯穿没有终止过,无论是...
    磬舟晔晔阅读 199评论 0 0
  • 雨又一次飘零 蒙蒙的夜空 细雨还是淋湿了我的心灵 是那一场蓦然回首的日子 我依旧错过了往日的光环 阳光海岸 但我依...
    米澜盛若阅读 215评论 0 5