Functors, Applicatives, And Monads 图解

来源:互联网 发布:砍柴网源码 编辑:程序博客网 时间:2024/05/21 22:38
PS:这篇文章是Haskell语言写的,所以很多API会不清楚,我们只是学习这种思想这里现在有一个简单的值:    

这里写图片描述

我们知道如何将函数应用于这个值:

这里写图片描述

很简单。让我们扩展一下,来说明任何值都可以在上下文中。现在,你可以把上下文想象成一个盒子,你可以把值放在里面:

这里写图片描述

现在,当您将一个函数应用到这个值时,您将根据上下文得到不同的结果。Functors、Applicatives, Monads, Arrows等都是基于此的思想。Maybe数据类型定义了两个相关的上下文:

这里写图片描述

很快我们将会看到:一个JUST A与之相对的另一个Nothing时候,函数应用有何不同的。首先让我们来谈谈Functors吧!PS;这里需要注意一下,因为这篇文章作者是kaskell写的,很多东西是您所熟悉的语言没有。解释一下。Maybe其实就是java与Scala中的Option,或者是javaScript的Promise,Swift中的Optional ,它就是我们在本文章中提到了容器,也可以叫“黑盒”,我们将值放进去,等于为该值定义了一个上下文环境。

1.Functors

当一个值被包装在一个上下文中,你不能对它应用一个正常的函数:

这里写图片描述

这就是fmap的切入点(注意。fmap是haskell的语法,我们还总结javaScript的语法,在javaScript中对应的就是map)。fmap是在街道上的,fmap熟悉上下文环境。fmap知道如何将函数应用到封装在上下文中的值。你可以对任何一个类型为 Functor 的类型使用 fmap.例如,假设您想要应用(+3)到Just 2上。如何使用fmap:
//hasKell>fmap (+3) (Just 2)Just 5//python 要先做准备工作$ pip3 install oslashpython3>>> from oslash import *>>> Just(2).map(lambda x: x+3)Just 5

这里写图片描述

Bam!fmap向我们展示它是怎么做的!但是fmap是怎么知道如何去应用函数的呢??

1.1 什么是Functor?

Functor是一个typeclass.这里是它的定义:

这里写图片描述

一个Functor可以是任何定义了fmap如何应用的数据类型,下面是fmap如何工作的:

这里写图片描述

所以我们可以这样做:
> fmap (+3) (Just 2)Just 5
fmap神奇地应用了这个函数,因为Maybe是一个Functor。它列明了如何可应用于Just和Nothing:
//haskell的fmapinstance Functor Maybe where    fmap func (Just val) = Just (func val)    fmap func Nothing = Nothing//下面的是javaScript的map的定义_.map = _.collect = function(obj, iteratee, context) {  iteratee = cb(iteratee, context);  var keys = !isArrayLike(obj) && _.keys(obj),      length = (keys || obj).length,      results = Array(length);  for (var index = 0; index < length; index++) {    var currentKey = keys ? keys[index] : index;    results[index] = iteratee(obj[currentKey], currentKey, obj);  }  return results;};
以下是我们写fmap (+3) (Just 2)的情况背后发生的事情:

这里写图片描述

所以你可以随你喜欢,fmap都是没问题的。你说为Nothing应用(+3)??

这里写图片描述

//haskell> fmap (+3) NothingNothing
就像《黑客帝国》中的莫菲斯.fmap知道应该做什么;你以Nothing开始,那么最终也是Nothing来结束!!现在可以理解为什么Maybe数据类型存在的意义了。例如,这里是你在一个没有Maybe的语言中与你操作数据库记录的方法:
post = Post.find_by_id(1)if post  return post.titleelse  return nilend
但是在Haskell中:
fmap (getPostTitle) (findPost 1)
如果findPost返回了一个post,我们将会通过getPostTitle来获取其标题。但是如果返回的是Nothing,那么我们最终也将会返回Nothing(我们刚才的实例有讲过)。这很优雅,不是吗(是挺优雅的,记不住呀)??<$>是fmap的中缀表达式版本,所以你经常会看到这样:
getPostTitle <$> (findPost 1)
下面是另一个例子:当您将一个函数应用到一个列表时会发生什么?

这里写图片描述

List仅仅是另一种让fmap以不同方式应用函数的上下文!列表也是Functor,这里是它的定义:
instance Functor [] where    fmap = map
好的,最后一个例子,当你将一个函数应用到另一个函数时会发生什么?
fmap (+3) (+1)
这里是一个函数

这里写图片描述

这是一个应用于另一个函数的函数:

这里写图片描述

结果是另外一个函数(果然函数语言不同呀,我还以为要报错呢,有点像Scala的函数字面量)!!
> import Control.Applicative> let foo = fmap (+3) (+2)> foo 1015
因此,函数也是Functor
instance Functor ((->) r) where    fmap f g = f . g
当你在函数上使用fmap时,你只是在做函数复合(就是函数的组合呗)!

2. Applicatives

Applicatives将它带到下一个层次。在Applicatives中,我们的值被包装在一个上下文中,就像Functors一样:

这里写图片描述

但我们的函数也被包装在一个上下文中!

这里写图片描述

好的,让我们来理解一下它。Applicatives没有开玩笑。Control.Applicative定义了<*>,它知道如何将一个封装在上下文中的函数应用到一个上下文中的值:

这里写图片描述

i.e:
Just (+3) <*> Just 2 == Just 5
使用<*>可以导致一些有趣的状况。比如:
> [(*2), (+3)] <*> [1, 2, 3][2, 4, 6, 4, 5, 6]

这里写图片描述

这里有一些是你能用 Applicative 做, 而无法用 Functor 做到的。你怎么才能把需要两个参数的函数应用到两个封装的值(就是我们刚才看到的包装在上下文中的值)上呢?
> (+) <$> (Just 5)Just (+5)> Just (+5) <$> (Just 4)ERROR ??? WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION WRAPPED IN A JUST

Applicatives:

> (+) <$> (Just 5)Just (+5)> Just (+5) <*> (Just 3)Just 8
Applicative将Functors推到一边,她说“大男孩可以以任意数量的参数来使用函数”。“有了 <$>和<*>,我可以取任意数量的未包装的参数值的函数。然后我把所有的包值都传递给它,然后得到一个包装的值。AHAHAHAHAH”
> (*) <$> Just 5 <*> Just 3Just 15
嘿!这里就有一个叫做liftA2的函数做了这样xiangtong 的事情
> liftA2 (*) (Just 5) (Just 3)Just 15

3. Monads

如何了解Monads:1.获得计算机科学博士学位2.把它扔掉,因为你不需要它!Monads增加了一个新的转折。Functors将函数应用于包装的值:

这里写图片描述

Applicatives将包装的函数应用于包装的值:

这里写图片描述

Monads为包装的值应用一个返回包装值的函数。Monads有一个函数>>=来做处理。让我们看一个例子。

这里写图片描述

假设half是一个只适用于偶数的函数:
half x = if even x           then Just (x `div` 2)           else Nothing

这里写图片描述

如果我们给它一个包装的值呢?

这里写图片描述

我们需要使用>>=将包装的值推入函数中。这是>>=的照片

这里写图片描述

它是如何工作的:
> Just 3 >>= halfNothing> Just 4 >>= halfJust 2> Nothing >>= halfNothing
内部发生了什么?Monads是另一个typeclass。这是一个部分的定义:
class Monad m where    (>>=) :: m a -> (a -> m b) -> m b
>>=在哪里:

这里写图片描述

因此,Maybe是一个Monads
instance Monad Maybe where    Nothing >>= func = Nothing    Just val >>= func  = func val
下面是我们使用Just 3所发生的事:

这里写图片描述

如果你传递一个Nothiong,它更简单:

这里写图片描述

你也可以把这些调用串起来:
> Just 20 >>= half >>= half >>= halfNothing

这里写图片描述

Cool(我咋觉得作者已经疯了)!!因此现在我们知道了Maybe是一个Functors,也是一个Applicative,还是一个Monad。现在让我们来看看另一个例子:IO monad:

这里写图片描述

明确的三个函数:getLine不接受参数,获取用户输入:

这里写图片描述

getLine :: IO String
readFile 获取一个字符串(一个文件名)并返回该文件的内容:

这里写图片描述

readFile :: FilePath -> IO String
putStrLn取一个字符串并打印它:

这里写图片描述

putStrLn :: String -> IO ()
这三个函数都具有一个常规值(或没有值),并返回一个包装的值。我们可以用>>=将他们穿起来 !

这里写图片描述

getLine >>= readFile >>= putStrLn
Haskell还为我们提供了一些关于monads的语法糖,叫做do表示法:
foo = do    filename <- getLine    contents <- readFile filename    putStrLn contents

4. 结论

1.一个functor就是一个实现了Functor typeclass的数据类型2.一个applicative 就是一个实现了Applicative typeclass的数据类型3.一个monad就是一个实现了Monad typeclass的数据类型4.Maybe是Functor ,Applicative ,它还是Monad 这三者的区别在哪??

这里写图片描述

functors:您可以使用fmap或<$>将函数应用于包装值applicatives:您可以使用<*>或liftA将一个包装的函数应用到包装的值monads:通过使用>>=或liftM为为包装的值应用一个函数,该函数返回一个包装的值所以,亲爱的朋友(我认为我们是朋友),我认为我们都同意monads 是很容易的,也是一个聪明的想法(tm)。
原创粉丝点击