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 oslash
python3
>>> 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的fmap
instance 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) Nothing
Nothing
就像《黑客帝国》中的莫菲斯.fmap知道应该做什么;你以Nothing开始,那么最终也是Nothing来结束!!现在可以理解为什么Maybe数据类型存在的意义了。例如,这里是你在一个没有Maybe的语言中与你操作数据库记录的方法:
post = Post.find_by_id(1)
if post
return post.title
else
return nil
end
但是在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 10
15
因此,函数也是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 3
Just 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 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing
内部发生了什么?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 >>= half
Nothing
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)。