这是个简单的值:
我们都知道怎么加一个函数应用到这个值上边:
很简单了. 我们来扩展一下, 让任意的值是在一个上下文当中. 现在的情况你可以想象一个可以把值放进去的盒子:
现在你把一个函数应用到这个值的时候,根据其上下文你会得到不同的结果. 这就是 Functor, Applicative, Monad, Arrow 之类概念的基础.Maybe数据类型定义了两种相关的上下文:
很快我们会看到对一个Justa和一个Nothing来说函数应用有何不同. 首先我们来说 Functor!
Functor
当一个值被封装在一个上下文里, 你就不能拿普通函数来应用:
就在这里fmap出现了.fmapis from the street,fmapis hip to contexts.fmap知道怎样将一个函数应用到一个带有上下文的值. 你可以对任何一个类型为Functor的类型使用fmap.
比如说, 想一下你想把(+3)应用到Just2. 用fmap:
>fmap(+3)(Just2)Just5
这是在幕后所发生的:
Bam!fmap告诉了我们那是怎么做到的!
So then you’re like, 好吧fmap, 请应用(+3)到一个Nothing?
>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 时发生了什么?
List 仅仅是另一种让fmap以不同方式应用函数的上下文!
Okay, okay, 最后一个例子: 你把一个函数应用到另一个函数时会发生什么?
fmap(+3)(+1)
这是个函数:
这是一个函数应用到另一个函数上:
结果就是又一个函数!
>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:
而我们的函数也被封装在了上下文里!
Yeah. Let that sink in. Applicative 并不是开玩笑.Control.Applicative定义了<*>, 这个函数知道怎样把封装在上下文里的函数应用到封装在上下文里的值:
也就是:
Just(+3)<*>Just2==Just5
使用<*>能带来一些有趣的情形. 比如:
>[(*2),(+3)]<*>[1,2,3][2,4,6,4,5,6]
这里有一些是你能用 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 应用封装过的函数到封装过的值:
Monads 应用会返回封装过的值的函数到封装过的值. Monad 有个>>=(念做 “bind”) 来做这个.
一起看个例子. Good ol’Maybeis a monad:
Just a monad hanging out
假定half是仅对偶数可用的函数:
half x=ifeven xthenJust(x`div`2)elseNothing
我们给它传入一个封装过的值会怎样?
我们要用到>>=, 用来强推我们封装过的值到函数里去. 这是>>=的照片:
它怎么起作用的:
>Just3>>=halfNothing>Just4>>=halfJust2>Nothing>>=halfNothing
其中发生了什么?
如果你传进一个Nothing就更简单了:
酷! 我们来看另一个例子: 那个IOmonad:
明确的三个函数.getLine获取用户输入而不接收参数:
getLine::IOString
readFile接收一个字符串 (文件名) 再返回文件的内容:
readFile::FilePath->IOString
putStrLn接收一个字符串打印:
putStrLn::String->IO()
这三个函数接收一个常规的值 (或者不接收值) 返回一个封装过的值. 我们可以用>>=把一切串联起来!
getLine>>=readFile>>=putStrLn
Aw yeah! 我们不需要在取消封装和重新封装 IO monad 的值上浪费时间.>>=为我们做了那些工作!
Haskell 还为 monad 提供了语法糖, 叫做do表达式:
foo=dofilename<-getLine
contents<-readFile filename
putStrLn contents
结论
functor:通过fmap或者<$>应用是函数到封装过的值
applicative:通过<*>或者liftA应用封装过的函数到封装过的值