函数式内功心法-00: 万物互连之monad创世纪

很多人学习haskell,都会在monad这个概念上迷失。真是天下苦monad久矣!

人们常常说 Monad不就是个自函子范畴上的幺半群么?
说这群人简单是丧心病狂,完全不过份。

那么monad到底是什么呢?
为什么说它解决了函数式的副作用?
为什么我们一定要学会monad?

其实一点也不难!

那么让我们开始主题:

  1. Monad内功心法
    a. 欢迎进入Monad
    b. Monad是什么?
    c. Monad的弟弟Applicative
    d: 彻底搞清楚复合连接技术
    e: Monad与Applicative的老婆MonadPlus, Alternative
    f. Monad的左膀右臂之Functor, Semigroup
  2. Monad能干啥?
    a. 为什么需要Monad?
    b. 如何用它解决函数式副作用?
    c. Monad玩起来
  3. 源码看透Monad类型特征
    a. Functor
    b. Semigroup
    c. Applicative
  4. 常用Monad类型源码分析
    a. State Monad
    b. 潘多拉之盒IO Monad
    c. maybe monad
    d: either monad
    e: list monad
    f: (->) monad
    g: state monad
    h: cont monad
    e: ...
  5. Monad Transformer库mtl
  6. Monad家族百花齐放之contraviant, bifunctor, biapplictive, profunctor
  7. comonad
  8. free monad

一. Monad内功心法

1. 欢迎进入Monad

在函数式编程世界里,一切都是不可变的。我们没有对象,没有赋值。一切都是全新的世界。
在这全新的世界里面,人们发现了一种技术,它极其简单,却无比强大。
它就是Monad, 一种以函数式为基础而生的复合技术!
想一想小孩子堆积木的玩法,用最简单的积木可以拼出机器人,高楼大厦,千万种变幻。
没错,这就是Monad! 汇小成多,不断复合,却是极其简单!

那么我们接着来进一步揭开monad的面纱.

2. Monad是什么?

Monad是一种类型特征,它的每个类型可以当作一个相互连接的黑盒子。

a. 首先来看什么是类型特征?

我们对于常用的String, Int, Double之类的类型应该比较熟悉了。
对于Int, Double类型来说,它们具有一些共同的特征,比如加减乘除之类的操作,我们就可以将这些特征抽象出来成为类型特征。

b. Monad的类型特征是什么?

Monad的类型特征就是,它是一个黑盒子,它可以连接复合。

  • 对于黑盒子,我们记为m a. 显式易见,m是盒子,a是里面的值。
  • 对于连接复合,我们记为m a >>= m b,它做什么操作呢?
    它吸取盒子a的值连接到盒子b:

m a -> (a -> m b) -> m b

可以把m当作积木块,a则是钩子值,a -> m b则是m a的钩子去连接m b, 最终复合出了新的积木。

3. Monad的弟弟Applicative

后来上面发现,复合技术并不只有Monad一种,还有一种更轻量级的Applicative。
那么我们看看Applicative又是怎么做到的呢?

  • 对于Applicative的黑盒子,我们记为f a
  • 对于连接复合,我们记为f a <*> f b, 它做什么操作呢?
    它穿透盒子f a连接到盒子f b:

f (a -> b) -> f a -> f b

可以把f当作电缆,a则是里面的电流,f(a->b)则是f a的电流穿透去连接f b, 最终连接了两段电缆。

4: 彻底搞清楚复合连接技术

我们以monad哥哥为例, applicative弟弟以此类推。

m a -> (a -> m b) -> m b

我们将m a通过a-> m b钩子传递连接到了m b,中间传递了钩子值a,产生了下一个钩子值b。
当然钩子值b可以继续向下连接复合。
这里面有几个点非常容易想错误:

    1. a是m a盒子的值!
      a不是盒子的值,a是盒子m a传递的值。想得到盒子a的值,就得用钥匙打开它。
      并不是所有盒子都能被打开的,所以也就有了后面的潘多拉之盒IO monad
    1. m a >>= m b的复合连接过程是顺序执行的!
      a不是m a盒子的值,m a盒子的值可以部分传递,亦可以完全传递,甚至根据条件传递。
      而m b盒子吸收传递值a之后,可以使其前面运行(顺序), 中间运行(嵌入), 尾部运行(逆序),这一切都是可能的。

显示易见, m a >>= m b过程中,我们将传递值a从m a盒子交接到m b盒子后构造出了传递值b。
而m a >>= m b复合连接出的新盒子的实际值需要钥匙打开才能得到。
如果盒子不能打开,我们将得不到它的实际值,但是我们可以使用它的传递值。
在大部分简单情况下,实际值会与传递值相等。
在小部分特殊情况下,我们打不开盒子,特别是潘多拉之盒IO monad。

5: Monad与Applicative的老婆MonadPlus, Alternative

前面讲了复合连接技术的基本形式。
在常用过程中为了使用,就产生了新的两种形式, MonadPlus 与Alternative.
它们是干什么的呢?

对于学过电路的人都会知道,任何复杂的电路,都可以分解为串行与并行。
当然这里的串行与并行是传递顺序,不是执行顺序。
顺序传递很容易理解,如果是顺序执行当然更容易理解了。
并行传递呢? 比如同级菜单,错误条件分支,之类的并行关系就是非常实用的

  • 对于Monad,我们有了顺序传递>>=.
    Monad顺理成章取了老婆 MonadPlus进行并行传递: m a plus m a.
    MonadPlus老婆说: 并行传递合二为一。
    并行传递的类型是相同的,所有:

m a -> m a -> m a

*对于弟弟Applicative呢,我们有了穿透传递f a <*> f b
Applicative也顺理成章取了老婆Alternative进行并行传递f a <|> f a
Applicative老婆也不甘示弱,大喊一声: 并行传递合二为一。

f a -> f a -> f a

一家人和和睦睦真是羡慕啊!

6. Monad的左膀右臂之Functor, Segmigroup

Monad的类型特征是可连接复合的黑盒子。它有两大助手,Functor与Segmigroup。
Functor就是黑盒子类型特征,或者人们所熟知的函子。
Semigroup就是复合连接类型特征,或者人们所熟知的半群。

  • Functor这个盒子我们定义为f a,它通过fmap 来对黑盒子进行操作:

(a -> b) -> f a -> f b

显而易见,我们对黑盒子f a通过函数a->b 操纵盒子公值a,得到新的公值b,私值依然是要自己打开的。

  • Semigroup的连接操作定义了 Semigroup a => a <> a

Semigroup a => a -> a -> a

显而易见,Semigroup类型特征的类型可以通过<>进行连接复合.

二. Monad能干啥?

1. 为什么需要Monad?

在纯函数式的编程世界里面,所有函数都是纯函数,每个值都是不可变的。
所以玩法很不一样,不能用其它的编程方式思考。

函数式编程规则比面向过程,面向对象编程规则简单一百倍,但是它的思维却是强大一百倍!

在haskell编程中,只有一种操作,就是连对盒子进行复合连接。

我们看一下haskell的hello world代码:

main :: IO ()
main =  putStrLn "hello monad" >> putStrLn "hello haskell"

haskell的入口就是main函数,main函数就是构造一个IO monad,即main :: IO ()
所以,整个haskell的运行过程就是构建一个IO monad, IO monad实现了monad类型特征。
main :: IO ()就是构建一个IO Monad,它的最终传递值是()。
()是一种存在的空值类型,void也是一种不存在空值类型,Nothing是一种空值数据。

显而易见, main函数就是连接复合出一个io monad,它的最终传递值是我们不需要关心的空值()。

在IO monad的盒子里面,每个连接组件都是IO monad,通过>>=连接。
putStrLn "hello monad" 是一个IO ()
putStrLn "hello haskell"也是一个IO ()
最终通过>>连接起来,>>是>>=的一种方便写法,就是丢弃不使用m a传递的钩子值a

当然我们可以使用do语法宏进行变换,书写起来更加可读.

main :: IO ()
main = do
  _ <- putStrLn "hello monad" 
  putStrLn "hello haskell"

这个就是将>>这种操作的值转换成->这种绑定变量操作,以后我们会经常看到。

是不是很简单,对,我们的编程就只这样一种简单操作:
a >>= (b mplus c mplus d) >>= e
通过最基础最简单的串并边电路逻辑构建出无比美妙的函数式新世界!

这里让我们大喊一声: Hello, Monad!

2. 如何用它解决函数式副作用?

monad以复合技术而生,以解决函数式副作用而闻名!
既然函数值不可变,我们读取文件会引起变化,随机数会变化,我们如何能一个纯函数的世界中生存下来呢?
那我们换一种方法考虑,我们的参数是可以变的,虽然相同的参数得到相同的值,读取不同时刻下面的文件其实是不同的参数。

这个随着环境变化的参数就叫RealWorld,由编译器提供,所以每次运行就产生了不同的值,解决了不可变与副作用的问题。

而只有IO这个潘多拉盒专用处理RealWorld,不能随意打开。

newtype IO a
  = GHC.Types.IO (GHC.Prim.State# GHC.Prim.RealWorld
                  -> (# GHC.Prim.State# GHC.Prim.RealWorld, a #))

所以,我们的整个编程过程其实很简单了。
构建可以处理RealWorld的IO monad盒子,然后复合连接成新的IO monad盒子,通过编译器提供的RealWorld变参,运行不同的IO monad的action,最终实现了不同RealWorld值下对应的副作用实现。
对于每一个IO行为则是一个State Monad,每进行一步处理,读取老折状态 ,产生了新的状态值。

简而言之, IO Monad读取编译器提供的RealWorld变参作为初始状态,它本身也是一个State Monad,每进行一步处理产生新的状态值,最终IO monad使用纯函数实现了副作用效果!

3. Monad玩起来

三. 源码看透Monad类型特征

四. 常用Monad类型源码分析

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

推荐阅读更多精彩内容

  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 8,985评论 0 13
  • 原文地址:Haskell学习-monad 什么是Monad Haskell是一门纯函数式的语言,纯函数的优点是安全...
    jeffzhong阅读 3,707评论 0 2
  • 什么是Monad Haskell是一门纯函数式的语言,纯函数的优点是安全可靠。函数输出完全取决于输入,不存在任何隐...
    猿学阅读 625评论 0 1
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • 梦晓书画阅读 171评论 0 5