聊一聊单子(Monad)

作者:Olivier Halligon,原文链接,原文日期:2015-10-17
译者:ray16897188;校对:小袋子;定稿:numbbbbb

之前的一篇文章中,我们用mapflatMap这两种基于OptionalArray类型的方法做了很多好玩儿的事情。但你可能并没有意识到,你已经在不自知的情况下使用了单子(单子,即 Monad:一个函数式编程的术语 - 译者注)。那么什么是单子?

什么是函子(Functors)和单子

我们在之前的一篇文章中得知了mapflatMap对于ArrayOptional来说有着相似的作用,甚至连函数签名都十分相似。

实际上这并不是一个特例:很多类型都有类似mapflatMap的方法,而这些方法都有那种类型的签名。这是一种十分常见的模式,这种模式的名字叫做单子

你可能之前在网上看过单子这个术语(也可能叫做函子),还看过尝试解释该术语的各种比喻。但是大部分比喻都让它更加复杂难懂。

事实上,单子和函子是非常简单的概念。它可以最终归结为:

一个函子是一种表示为Type<T>的类型,它:

  • 封装了另一种类型(类似于封装了某个T类型的Array<T>Optional<T>
  • 有一个具有(T->U) -> Type<U>签名的map方法

一个单子是一种类型,它:

  • 是一个函子(所以它封装了一个T类型,拥有一个map方法)
  • 还有一个具有(T->Type<U>) -> Type<U>签名的flatMap方法

这就是对单子函子所需要了解的一切!一个单子就是一种带有flatMap方法的类型,一个函子就是一种带有一个map方法的类型。**很简单,不是么?

各种类型的单子

你已经学过两种既是函子又是单子的类型,它们是:Array<T>Optional<T>。当然,这样的类型还有很多。

实际上这些类型的方法会有其他的名字,不限于mapflatMap。例如一个Promise也是一个单子,而它的相对应的mapflatMap方法叫做then

仔细看一下Promise<T>then方法签名,思考一下:它拿到未来返回的值T,进行处理,然后要么返回一个新类型U,要么返回一个封装了这个新类型的、新的Promise<U>... 没错,我们又一次得到了相同的方法签名,所以Promise实际上也是一个单子

有很多类型都符合单子的定义。比如ResultSignal,... 你还可以想到更多(如果需要的话你甚至可以创建你自己的单子)。

看出相似性了吗?(为方便对比加了空格)

// Array, Optional, Promise, Result 都是函子
   anArray     .map( transform: T ->          U  ) ->    Array<U>
anOptional     .map( transform: T ->          U  ) -> Optional<U>
 aPromise     .then( transform: T ->          U  ) ->  Promise<U>
   aResult     .map( transform: T ->          U  ) ->   Result<U>

// Array, Optional, Promise, Result 都是单子
   anArray .flatMap( transform: T ->    Array<U> ) ->    Array<U>
anOptional .flatMap( transform: T -> Optional<U> ) -> Optional<U>
  aPromise    .then( transform: T ->  Promise<U> ) ->  Promise<U>
   aResult .flatMap( transform: T ->   Result<U> ) ->   Result<U>

map()flatMap()级联起来

通常你还可以把这两个方法级联,这会使它们更加强大。例如,最开始你有一个Array<T>,通过使用map来对它做转换操作,得到一个Array<U>,然后对这个Array<U>再级联上一个map,对它做另一个转换操作将其转换成一个Array<Z>,等等。这会让你的代码看起来就像是在生产线上一样:把一个初始值拿来,让他经过一系列的黑盒子处理,然后得到一个最终的结果。这时你就可以说你实际上是在做函数式编程了!

下面是一个示范如何将mapflatMap的调用级联起来去做多次转换的例子。我们从一个字符串开始,把它按单词分开,然后依次做如下转换:

  1. 统计每个单词的字符个数,做计数
  2. 把每个计数转换成一个相对应的单词
  3. 给每个结果加个后缀
  4. 对每个字符串结果做%转义
  5. 把每个字符串结果转换成一个NSURL
let formatter = NSNumberFormatter()
formatter.numberStyle = .SpellOutStyle
let string = "This is Functional Programming"
let translateURLs = string
    // Split the characters into words
    .characters.split(" ")
    // Count the number of characters on each word
    .map { $0.count }
     // Spell out this number of chars (`stringFromNumber` can return nil)
    .flatMap { (n: Int) -> String? in formatter.stringFromNumber(n) }
     // add " letters" suffix
    .map { "\($0) letters" }
    // encode the string so it can be used in an NSURL framgment after the # (the stringByAdding… method can return nil)
    .flatMap { $0.stringByAddingPercentEncodingWithAllowedCharacters(.URLFragmentAllowedCharacterSet()) }
    // Build an NSURL using that string (`NSURL(string: …)` is failable: it can return nil)
    .flatMap { NSURL(string: "https://translate.google.com/#auto/fr/\($0)") }

print(translateURLs)
// [https://translate.google.com/#auto/fr/four%20letters, https://translate.google.com/#auto/fr/two%20letters, https://translate.google.com/#auto/fr/ten%20letters, https://translate.google.com/#auto/fr/eleven%20letters]

上面这段代码可能需要你研究一会儿,尝试去理解每一个中间阶段的mapflatMap的签名是什么,并搞清楚每一步都发生了什么事。

但无论如何,你能看出来对于描述一系列处理流程来说,这是一种很好的方式。这种方式可以被看做是一条生产线,从原材料开始,然后对它做多种转换,最终在生产线的尽头拿到成品

结论

尽管看起来很吓人,但单子很简单。

但实际上,你怎么叫它们都没关系。只要你知道如果你想把一种封装类型转换成另一种,而某些类型的mapflatMap方法着实能帮到你,这就够了。


这篇文章是"Swift编程思想"系列的后记。别担心,我还会写很多文章,论述 Swift 在其他应用场景下的美妙之处,不过我不会再拿这些和 ObjC 比较了(因为 Swift 真的好太多了,你现在应该完全把 ObjC 忘掉了 😄)。

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

推荐阅读更多精彩内容