前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Functors, Applicatives, And Monads In PicturesFunctors, Applicatives, And Monads In Pictures

Functors, Applicatives, And Monads In PicturesFunctors, Applicatives, And Monads In Pictures

作者头像
一个会写诗的程序员
发布2018-12-12 13:49:59
5540
发布2018-12-12 13:49:59
举报

Functors, Applicatives, And Monads In Pictures

原文: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html 参考文章: http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf


Here's a simple value:

image

And we know how to apply a function to this value:

image

Simple enough. Lets extend this by saying that any value can be in a context. For now you can think of a context as a box that you can put a value in:

image

Now when you apply a function to this value, you'll get different results depending on the context. This is the idea that Functors, Applicatives, Monads, Arrows etc are all based on. The Maybe data type defines two related contexts:

image

代码语言:javascript
复制
data Maybe a = Nothing | Just a

In a second we'll see how function application is different when something is a Just a versus a Nothing. First let's talk about Functors!

Functors

When a value is wrapped in a context, you can't apply a normal function to it:

image

This is where fmap comes in. fmap is from the street, fmap is hip to contexts. fmap knows how to apply functions to values that are wrapped in a context. For example, suppose you want to apply (+3) to Just 2. Use fmap:

代码语言:javascript
复制
> fmap (+3) (Just 2)
Just 5

image

Bam! fmap shows us how it's done! But how does fmap know how to apply the function?

Just what is a Functor, really?

Functor is a typeclass. Here's the definition:

image

A Functor is any data type that defines how fmap applies to it. Here's how fmap works:

image

So we can do this:

代码语言:javascript
复制
> fmap (+3) (Just 2)
Just 5

And fmap magically applies this function, because Maybe is a Functor. It specifies how fmapapplies to Justs and Nothings:

代码语言:javascript
复制
instance Functor Maybe where
    fmap func (Just val) = Just (func val)
    fmap func Nothing = Nothing

Here's what is happening behind the scenes when we write fmap (+3) (Just 2):

image

So then you're like, alright fmap, please apply (+3) to a Nothing?

image

代码语言:javascript
复制
> fmap (+3) Nothing
Nothing

Bill O'Reilly being totally ignorant about the Maybe functor

Like Morpheus in the Matrix, fmap knows just what to do; you start with Nothing, and you end up with Nothing! fmap is zen. Now it makes sense why the Maybe data type exists. For example, here's how you work with a database record in a language without Maybe:

代码语言:javascript
复制
post = Post.find_by_id(1)
if post
  return post.title
else
  return nil
end

But in Haskell:

代码语言:javascript
复制
fmap (getPostTitle) (findPost 1)

If findPost returns a post, we will get the title with getPostTitle. If it returns Nothing, we will return Nothing! Pretty neat, huh? <$> is the infix version of fmap, so you will often see this instead:

代码语言:javascript
复制
getPostTitle <$> (findPost 1)

Here's another example: what happens when you apply a function to a list?

image

Lists are functors too! Here's the definition:

代码语言:javascript
复制
instance Functor [] where
    fmap = map

Okay, okay, one last example: what happens when you apply a function to another function?

代码语言:javascript
复制
fmap (+3) (+1)

Here's a function:

image

Here's a function applied to another function:

image

The result is just another function!

代码语言:javascript
复制
> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15

So functions are Functors too!

代码语言:javascript
复制
instance Functor ((->) r) where
    fmap f g = f . g

When you use fmap on a function, you're just doing function composition!

Applicatives

Applicatives take it to the next level. With an applicative, our values are wrapped in a context, just like Functors:

image

But our functions are wrapped in a context too!

image

Yeah. Let that sink in. Applicatives don't kid around. Control.Applicative defines <*>, which knows how to apply a function wrapped in a context to a value wrapped in a context:

image

i.e:

代码语言:javascript
复制
Just (+3) <*> Just 2 == Just 5

Using <*> can lead to some interesting situations. For example:

代码语言:javascript
复制
> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]

image

Here's something you can do with Applicatives that you can't do with Functors. How do you apply a function that takes two arguments to two wrapped values?

代码语言:javascript
复制
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION WRAPPED IN A JUST

Applicatives:

代码语言:javascript
复制
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <*> (Just 3)
Just 8

Applicative pushes Functor aside. "Big boys can use functions with any number of arguments," it says. "Armed <$> and <*>, I can take any function that expects any number of unwrapped values. Then I pass it all wrapped values, and I get a wrapped value out! AHAHAHAHAH!"

代码语言:javascript
复制
> (*) <$> Just 5 <*> Just 3
Just 15

And hey! There's a function called liftA2 that does the same thing:

代码语言:javascript
复制
> liftA2 (*) (Just 5) (Just 3)
Just 15

Monads

How to learn about Monads:

  1. Get a PhD in computer science.
  2. Throw it away because you don't need it for this section!

Monads add a new twist.

Functors apply a function to a wrapped value:

image

Applicatives apply a wrapped function to a wrapped value:

image

Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.

Let's see an example. Good ol' Maybe is a monad:

Just a monad hanging out

Suppose half is a function that only works on even numbers:

代码语言:javascript
复制
half x = if even x
           then Just (x `div` 2)
           else Nothing

image

What if we feed it a wrapped value?

image

We need to use >>= to shove our wrapped value into the function. Here's a photo of >>=:

image

Here's how it works:

代码语言:javascript
复制
> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing

What's happening inside? Monad is another typeclass. Here's a partial definition:

代码语言:javascript
复制
class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b

Where >>= is:

image

So Maybe is a Monad:

代码语言:javascript
复制
instance Monad Maybe where
    Nothing >>= func = Nothing
    Just val >>= func  = func val

Here it is in action with a Just 3!

image

And if you pass in a Nothing it's even simpler:

image

You can also chain these calls:

代码语言:javascript
复制
> Just 20 >>= half >>= half >>= half
Nothing

image

image

Cool stuff! So now we know that Maybe is a Functor, an Applicative, and a Monad.

Now let's mosey on over to another example: the IO monad:

image

Specifically three functions. getLine takes no arguments and gets user input:

image

代码语言:javascript
复制
getLine :: IO String

readFile takes a string (a filename) and returns that file's contents:

image

代码语言:javascript
复制
readFile :: FilePath -> IO String

putStrLn takes a string and prints it:

image

代码语言:javascript
复制
putStrLn :: String -> IO ()

All three functions take a regular value (or no value) and return a wrapped value. We can chain all of these using >>=!

image

代码语言:javascript
复制
getLine >>= readFile >>= putStrLn

Aw yeah! Front row seats to the monad show!

Haskell also provides us with some syntactical sugar for monads, called do notation:

代码语言:javascript
复制
foo = do
    filename <- getLine
    contents <- readFile filename
    putStrLn contents

Conclusion

  1. A functor is a data type that implements the Functor typeclass.
  2. An applicative is a data type that implements the Applicative typeclass.
  3. A monad is a data type that implements the Monad typeclass.
  4. A Maybe implements all three, so it is a functor, an applicative, and a monad.

What is the difference between the three?

image

  • functors: you apply a function to a wrapped value using fmap or <$>
  • applicatives: you apply a wrapped function to a wrapped value using <*> or liftA
  • monads: you apply a function that returns a wrapped value, to a wrapped value using >>= or liftM

So, dear friend (I think we are friends by this point), I think we both agree that monads are easy and a SMART IDEA(tm). Now that you've wet your whistle on this guide, why not pull a Mel Gibson and grab the whole bottle. Check out LYAH's section on Monads. There's a lot of things I've glossed over because Miran does a great job going in-depth with this stuff.

Translations

This post has been translated into:

Human languages:

Programming languages:

If you translate this post, send me an email and I'll add it to this list!

For more monads and pictures, check out three useful monads.

FP , 又称为 Monadic Programming , 泛函编程。

不同类型的Monad实例则会支持不同的程序运算行为,如:Option Monad在运算中如果遇到None值则会中途退出;State Monad会确保状态值会伴随着程序运行流程直到终结;List Monad运算可能会产生多个结果等等。Scalaz提供了很多不同种类的Monad如:StateMonad, IOMonad, ReaderMonad, WriterMonad,MonadTransformer等等。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.11.15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Functors, Applicatives, And Monads In Pictures
    • Functors
      • Just what is a Functor, really?
        • Applicatives
          • Monads
            • Conclusion
              • Translations
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档