2016/11/3 Haskell

    最近很长的一段时间,学习都卡壳在Functor和Applicative上,弄不懂这俩到底是啥,根本没有办法继续去学习Monad。直到今天,我无意中看到了Aditya Bhargava写的《Functors, Applicatives, And Monads In Pictures》,上面有非常多的图来阐述具体这玩意是啥。

   我就把它翻译成中文(不过事先声明,未必翻译得准确,可能有毒)


   这里有一个很简单的值

    

   并且我们也知道怎么让一个函数应用这个值

    

   足够简单,现在让我们继续扩展他,试着把上下文想象成一个盒子,然后我们把这个值放入到盒子中。

    

   现在,当你的函数接收值(参数)时,你将根据你的上下文得到不同的结果,Functors、Applicatives、Monads、Arrows等等都是基于这个想法。Maybe类型关联了两种上下文。

    

      data Maybe a = Nothing | Just a

   在第二部分,我们将看出Just a和Nothing之间的区别,先让我们谈论一下Functor。

 

   Functors

   当一个值被装进一个盒子里,你是无法让一个普通的函数去应用它。

    

   这就引出了fmap。fmap来自街道上(fmap is from the street),它非常熟悉上下文,它知道如何让函数接收被包在上下文(盒子)中的值,举例来说,你想让(+3)函数应用Just 2,使用fmap。

    > fmap (+3) (Just 2)
    Just 5

   

  砰!fmap给我们展示了它如何做到的!但fmap又是如何知道怎样处理函数的?

 

  一个Functor是什么,真的?

  Functor是一个类型类,这里是它的定义

   

  下面是解析fmap是如何工作的

    

   因此我们可以这么做:

      > fmap (+3) (Just 2)
      Just 5

   fmap神奇的应用了这个函数,因为Maybe是一个Functor,它指定了fmap处理Just s和Nothing s

      instance Functor Maybe where
          fmap func (Just val) = Just (func val)
          fmap func Nothing = Nothing

   当我们执行fmap (+3) (just 2)的场景会发生了什么事    

   如果你喜欢,好的fmap请让Nothing应用于(+3)?

    

  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返回一篇文章,你将通过getPostTitle来获得它的标题;如果它返回的是Nothing,那我们将返回Nothing,很灵活对吧。<$>中缀版的fmap,因此你也会惊颤看到这种形式:

      getPostTitle <$> (findPost 1)

   这里还有另外一个例子,关于当函数接收到一个List会发生什么事的       

   List同样也是一个functors,这里是它的定义

      instance Functor [] where
          fmap = map

   好的好的,最后一个例子,当一个一个函数被应用到另外一个函数时会发生什么事

      fmap (+3) (+1)

   这是一个函数

    

   这个函数被另外一个函数应用

    

   结果得到了一个新函数(连起来了,人体蜈蚣?

      > import Control.Applicative
      > let foo = fmap (+3) (+2)
      > foo 10
      15

   因此functions也是Functor。

      instance Functor ((->) r) where
          fmap f g = f . g

   当你使用fmap接收函数参数,其实你是在做函数的合成。

 

   Applicatives

   Applicatives将它提升到下一个层次,借助Applicative,我们的值可以包装到上下文中,就像Functors那样。

   

   但我们的函数也是被包装进一个上下文中。

   

   是的,把Control Applicative定义成<*>,它知道如何把上下文中包装好的值应用于包装好的函数中。    

   譬如:

      Just (+3) <*> Just 2 == Just 5

   使用<*>可以是状况变得有趣,譬如: 

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

  这里你有一些Applicatives是Functors中无法完成的。你如何把两个包装好的值应用到一个接收两个参数的包装好的函数里面?

      > (+) <$> (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把Functor推开一边说“大个子可以使用任意参数个数的函数”它说“武装<$>和<*>,我可以使用期望任意没有经过包装的参数个数的函数,然后我传递它所有包装里的并得到一个被包装的值输出!哈哈哈哈!”

      > (*) <$> Just 5 <*> Just 3
      Just 15

   当然,这里有个函数叫liftA2,它也是做同样的事情。

      > liftA2 (*) (Just 5) (Just 3)
      Just 15

  

   Monads

   如何学习Monads:

    1、获得一个计算机博士学位

    2、不学它,因为你根本用不到它

   Monads产生一个新的转折

   Functors让一个被包装的值应用到一个函数中

   

   Applicatives让一个被包装d值应用到一个被包装的函数中

   

   Monads把一个被包装的值应用到一个会返回被包装了的值的函数中。

   让我们来看一个例子。Maybe就是一个Monad

   

   假设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

   在里面发生了什么?这里是它的定义:

      class Monad m where
          (>>=) :: m a -> (a -> m b) -> m b

   >>= 是

   

   

   因此Maybe也是Monad

      instance Monad Maybe where
          Nothing >>= func = Nothing
          Just val >>= func  = func val

   这是是Just3的动作!

   

   如果你输入一个Nothing,这就更简单了

   

   你也可以使用这种调用方式:

      > Just 20 >>= half >>= half >>= half
      Nothing

   

   现在你知道Maybe是一个Functor,是一个Applicative,也是一个Monad

   现在让我们再看一个案例:IO Monad

   

   三个特别的函数,getLine:不需要参数,获得用户的输入

    

      getLine :: IO String

   readFile:需要一个字符串(文件名)参数,返回文件的内容

    

      readFile :: FilePath -> IO String

     putStrLn:输出一行字符串

    

      putStrLn :: String -> IO ()

    所有这三个函数都需要一个值(或者不需要值)并返回一个被包装的值,我们可以用>>=把它们捆绑起来。

    

      getLine >>= readFile >>= putStrLn

    Haskell还给我们提供了一些称之为do的Monad语法糖

      foo = do
        filename <- getLine
        contents <- readFile filename
        putStrLn contents

 

  结论

   1、一个functor是实现Functor数据类型的数据类型

   2、一个applicative是实现Applicative数据类型的数据类型

   3、一个monad是实现Monad数据类型的数据类型

   4、Maybe实现了以上三种,所以它既是一个Functor也是一个Applicative还是一个Monad

   这三个之间的区别是什么?    

   functors:你可以使用fmap或者<$>让一个被包装的值应用于函数当中

   applicatives:你可以使用<*>或者liftA让一个被包装的值应用于被包装的函数中

   monads:你可以使用liftM或者>>=让被包装的值应用于被包装了的函数中并返回一个被包装的值


   基本上就是这样了,英文水平有限,翻译得未必很好,见谅见谅。 

  [ Haskell ]   [ Functor ]   [ Applicative ]   [ Monad ]
知识共享许可协议 本作品由小蝶惊鸿创作,采用知识共享署名 4.0 国际许可协议进行许可,转载时请保留本文署名及链接。