[zz]Functor, Applicative, 以及 Monad 的图片阐释

这是个简单的值:

我们都知道怎么加一个函数应用到这个值上边:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第1张图片

很简单了. 我们来扩展一下, 让任意的值是在一个上下文当中. 现在的情况你可以想象一个可以把值放进去的盒子:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第2张图片

现在你把一个函数应用到这个值的时候,根据其上下文你会得到不同的结果. 这就是 Functor, Applicative, Monad, Arrow 之类概念的基础.Maybe数据类型定义了两种相关的上下文:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第3张图片

很快我们会看到对一个Justa和一个Nothing来说函数应用有何不同. 首先我们来说 Functor!

Functor

当一个值被封装在一个上下文里, 你就不能拿普通函数来应用:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第4张图片

就在这里fmap出现了.fmapis from the street,fmapis hip to contexts.fmap知道怎样将一个函数应用到一个带有上下文的值. 你可以对任何一个类型为Functor的类型使用fmap.

比如说, 想一下你想把(+3)应用到Just2. 用fmap:

>fmap(+3)(Just2)Just5

这是在幕后所发生的:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第5张图片

Bam!fmap告诉了我们那是怎么做到的!

So then you’re like, 好吧fmap, 请应用(+3)到一个Nothing?

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第6张图片

>fmap(+3)NothingNothing

就像 Matrix 里的 Morpheus,fmap就是知道要做什么; 你从Nothing开始, 那么你再由Nothing结束!fmap是禅. So now you’re all like,准确说究竟什么是 Functor?嗯, Functor 就是任何能用fmap操作的数据类型. 因此Maybe是个 functor. 而且我们很快会看到, list 也是 functor.

这样上下文存在就有意义了. 比如, 这是在没有Maybe的语言里你操作一个数据库记录的方法:

post=Post.find_by_id(1)ifpostreturnpost.titleelsereturnnilend

但用 Haskell:

fmap(getPostTitle)(findPost1)

如果findPost返回一条 post, 我们就通过getPostTitle得到了 title. 如果返回的是Nothing, 我们加e得到Nothing! 非常简洁, huh?<$>是fmap的中缀表达式版本, 所以你经常是会看到:

getPostTitle<$>(findPost1)

另一个例子: 当你把函数应用到 list 时发生了什么?

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第7张图片

List 仅仅是另一种让fmap以不同方式应用函数的上下文!

Okay, okay, 最后一个例子: 你把一个函数应用到另一个函数时会发生什么?

fmap(+3)(+1)

这是个函数:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第8张图片

这是一个函数应用到另一个函数上:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第9张图片

结果就是又一个函数!

>importControl.Applicative>let foo=(+3)<$>(+2)>foo1015

这就是函数复合! 就是说,f<$>g==f.g!

注意:目前为止我们做的是将上下文当作是一个容纳值的盒子. But sometimes the box analogy wears a little thin. 特别要记住: 盒子是有效的记忆图像, 然呵又是你并没有盒子. 有时你的 “盒子” 是个函数.

Applicative

Applicative 把这带到了一个新的层次. 借助 applicative, 我们的 values 就被封装在了上下文里, 就像 Functor:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第10张图片

而我们的函数也被封装在了上下文里!

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第11张图片

Yeah. Let that sink in. Applicative 并不是开玩笑.Control.Applicative定义了<*>, 这个函数知道怎样把封装在上下文里的函数应用到封装在上下文里的值:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第12张图片

也就是:

Just(+3)<*>Just2==Just5

使用<*>能带来一些有趣的情形. 比如:

>[(*2),(+3)]<*>[1,2,3][2,4,6,4,5,6]

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第13张图片

这里有一些是你能用 Applicative 做, 而无法用 Functor 做到的. 你怎么才能把需要两个参数的函数应用到两个封装的值上呢?

>(+)<$>(Just5)Just(+5)>Just(+5)<$>(Just4)ERROR???WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION WRAPPED IN A JUST

Applicative:

>(+)<$>(Just5)Just(+5)>Just(+5)<*>(Just3)Just8

Applicative把Functor推到了一边. “大腕儿用得起任意个参数的函数,” 他说. “用<$>和<*>武装之后, 我可以接受需要任何个未封装的值的函数. 然后我传进一些封装过的值, 再我就得到一个封装的值的输出! AHAHAHAHAH!”

>(*)<$>Just5<*>Just3Just15

一 applicative 看着一 functor 应用一函数

还有啦! 有一个叫做liftA2的函数也做一样的事:

>liftA2(*)(Just5)(Just3)Just15

Monad

如何学习 Monad:

拿个计算机科学的 PhD.

把她抛在一边, 因为这个章节里你用不到她!

Monads add a new twist.

Functor 应用函数到封装过的值:

Applicative 应用封装过的函数到封装过的值:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第14张图片

Monads 应用会返回封装过的值的函数到封装过的值. Monad 有个>>=(念做 “bind”) 来做这个.

一起看个例子. Good ol’Maybeis a monad:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第15张图片

Just a monad hanging out

假定half是仅对偶数可用的函数:

half x=ifeven xthenJust(x`div`2)elseNothing

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第16张图片

我们给它传入一个封装过的值会怎样?

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第17张图片

我们要用到>>=, 用来强推我们封装过的值到函数里去. 这是>>=的照片:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第18张图片

它怎么起作用的:

>Just3>>=halfNothing>Just4>>=halfJust2>Nothing>>=halfNothing

其中发生了什么?

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第19张图片

如果你传进一个Nothing就更简单了:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第20张图片
[zz]Functor, Applicative, 以及 Monad 的图片阐释_第21张图片

酷! 我们来看另一个例子: 那个IOmonad:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第22张图片

明确的三个函数.getLine获取用户输入而不接收参数:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第23张图片

getLine::IOString

readFile接收一个字符串 (文件名) 再返回文件的内容:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第24张图片

readFile::FilePath->IOString

putStrLn接收一个字符串打印:

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第25张图片

putStrLn::String->IO()

这三个函数接收一个常规的值 (或者不接收值) 返回一个封装过的值. 我们可以用>>=把一切串联起来!

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第26张图片

getLine>>=readFile>>=putStrLn

Aw yeah! 我们不需要在取消封装和重新封装 IO monad 的值上浪费时间.>>=为我们做了那些工作!

Haskell 还为 monad 提供了语法糖, 叫做do表达式:

foo=dofilename<-getLine

contents<-readFile filename

putStrLn contents

结论

[zz]Functor, Applicative, 以及 Monad 的图片阐释_第27张图片

functor:通过fmap或者<$>应用是函数到封装过的值

applicative:通过<*>或者liftA应用封装过的函数到封装过的值

你可能感兴趣的:([zz]Functor, Applicative, 以及 Monad 的图片阐释)