在>> = Monad运算符的签名上 [英] On the signature of >>= Monad operator

查看:116
本文介绍了在>> = Monad运算符的签名上的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

 >> = :: Monad m => m a  - > (a  - > m b) - > mb 

问题是为什么函数的类型是

 (a  - > mb)

 (a  - &b; b)

我会说后者更实用,因为它允许直接集成已定义monad中现有的纯函数。



相反,编写一个通用的适配器似乎并不困难。

  adapt ::(Monad m)=> ; (a  - > b) - > (a  - > mb)

但无论如何,我认为您可能已有(a - > b)而不是(a - > mb)



注意。我解释我的意思是实际和可能。
如果你还没有在程序中定义任何monad,那么你的函数是纯(a - > b),你会仅仅因为你还没有定义 m ,所以有<0 c $ c>(a - > mb)类型的0个函数。如果你决定定义一个monad m ,就需要有新的 a - > mb 函数定义。

解决方案

基本上,(>> = )允许您按照后面的操作可以根据先前的结果选择不同行为的方式对操作进行排序。您可以在 Functor typeclass中找到更加纯粹的函数,可以使用(>> =),但如果你一个人坚持下去,你将不再能够排序操作。还有一个名为 Applicative 的中间程序,它允许您对顺序操作进行排序,但不会根据中间结果进行更改。



作为一个例子,让我们建立一个简单的IO操作类型,从Functor到Applicative到Monad。






我们将重点关注 GetC 如下

  GetC a = Pure a | GetC(Char  - > GetC a)

第一个构造函数在时间上有意义,一个应该立即有意义 - GetC 拥有一个函数可以响应传入的字符。我们可以将 GetC 转换为 IO 动作以提供这些字符

  io :: GetC a  - > IO a 
io(Pure a)=返回a
io(GetC go)= getChar>> =(\char-> io(go char))

这就清楚了 Pure 来自哪里 - 它处理纯粹的值在我们的类型。最后,我们将使 GetC 摘要:我们将禁止使用 Pure 或<$ c $直接访问c> GetC ,并让我们的用户只能访问我们定义的函数。我会写出最重要的一个

  getc :: GetC Char 
getc = GetC Pure

获取角色后立即考虑的函数是一个纯粹的值。虽然我把它称为最重要的功能,但很明显,现在 GetC 是相当无用的。我们所能做的就是运行 getc ,接着执行 io ...,以得到一个完全等于 getChar

  io getc === getChar :: IO Char 

但是我们会从这里开始建立。






如前所述, Functor typeclass提供了一个与您正在寻找的函数完全相同的函数,称为 fmap

  class Functor f其中
fmap ::(a - > b) - > f a - > fb

事实证明,我们可以实例化 GetC 作为 Functor 所以让我们做到这一点。

 实例Functor GetC where 
fmap f(Pure a)= Pure(fa)
fmap f(GetC go)= GetC(\char-> fmap f(go char))
fmap
会影响

>纯粹的构造函数。在 GetC 构造函数中,它只是被推下并推迟到稍后。这暗示了 fmap 的弱点,但让我们试试看。

  io getc :: IO char 
io(fmap ord getc):: IO Int
io(fmap(\c - > ord + 1)getc):: IO Int

我们可以修改 IO 我们的类型的解释,但这就是它!特别是,我们仍然仅限于获取单个字符,然后运行回< IO 来做任何有趣的事情。



这是 Functor 的弱点。正如你所指出的那样,它只处理纯函数,它在计算结束时被卡住,只修改 Pure 构造函数。






下一步是 Applicative ,它扩展了 Functor like this

  class Functor f =>应用f其中
pure :: a - > f a
(*):: f(a - > b) - > f a - > fb

换句话说,它扩展了将纯数值注入到我们的上下文中的概念允许纯函数应用程序跨越数据类型。不出所料, GetC 实例化应用程序

 实例应用GetC其中
pure = Pure
纯f * Pure x = Pure(f x)
GetC gof< *> getcx = GetC(\ char - > gof<> getcx)
Pure f< GetC gox = GetC(\char-> fmap f(gox char))

Applicative允许我们对序列操作进行排序并且可能已经从定义中清楚了。实际上,我们可以看到(< *>)向前推送字符应用程序,使得 GetC 按顺序执行((*))的任一侧。我们使用 Applicative 就像这样

  fmap(,)getc <* > getc :: GetC(Char,Char)

它可以让我们构建令人难以置信的有趣函数,更多比 Functor 要复杂。例如,我们已经可以形成一个循环并获得无限的字符流。

  getAll :: GetC [Char] 
getAll = fmap(:) getc< *> getAll

它演示了 Applicative 的性质能够一个接一个地排序。



问题是我们无法停下来。 io getAll 是一个无限循环,因为它永远消耗字符。例如,当它看到'\ n'时,我们不能说它停止,因为 Applicative s序列没有注意到更早的结果。






所以让我们走最后一步实例 Monad
pre $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $实例Monad GetC其中
return =纯
纯a>> = f = fa
GetC go>> = f = GetC(\ char - > go char>> = f)

这允许我们立即实施一个停止 getAll

  getLn :: GetC String 
getLn = getc>> = \c - >案例c的
'\\\
' - > return []
s - > fmap(s :) getLn

或者,使用 do notation

  getLn :: GetC字符串
getLn = do
c< - getc
案例c的
'\ n' - > return []
s - > fmap(s :) getLn

那么给出了什么?为什么我们现在可以编写一个停止循环?



由于(>> =):: m a - > (a - > m b) - > m b 让第二个参数为纯值的函数选择下一个动作 m b 。在这种情况下,如果传入的字符是'\ n',我们选择 return [] 并终止循环。因此,这就是为什么您可能希望 Monad 超过<$ c $的原因。

C>函子
。这个故事还有很多,但这些都是基础。


This is the signature of the well know >>= operator in Haskell

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

The question is why type of the function is

(a -> m b)

instead of

(a -> b)

I would say the latter one is more practical because it allows straightforward integration of existing "pure" functions in the monad being defined.

On the contrary, it seems not difficult to write a general "adapter"

adapt :: (Monad m) => (a -> b) -> (a -> m b)

but anyway I regard more probable that you already have (a -> b) instead of (a -> m b).

Note. I explain what I mean by "pratical" and "probable". If you haven't defined any monad in a program yet, then, the functions you have are "pure" (a -> b) and you will have 0 functions of the type (a -> m b) just because you have not still defined m. If then you decide to define a monad m it comes the need of having new a -> m b functions defined.

解决方案

Basically, (>>=) lets you sequence operations in such a way that latter operations can choose to behave differently based on earlier results. A more pure function like you ask for is available in the Functor typeclass and is derivable using (>>=), but if you were stuck with it alone you'd no longer be able to sequence operations at all. There's also an intermediate called Applicative which allows you to sequence operations but not change them based on the intermediate results.

As an example, let's build up a simple IO action type from Functor to Applicative to Monad.


We'll focus on a type GetC which is as follows

GetC a = Pure a | GetC (Char -> GetC a)

The first constructor will make sense in time, but the second one should make sense immediately—GetC holds a function which can respond to an incoming character. We can turn GetC into an IO action in order to provide those characters

io :: GetC a -> IO a
io (Pure a)  = return a
io (GetC go) = getChar >>= (\char -> io (go char))

Which makes it clear where Pure comes from---it handles pure values in our type. Finally, we're going to make GetC abstract: we're going to disallow using Pure or GetC directly and allow our users access only to functions we define. I'll write the most important one now

getc :: GetC Char
getc = GetC Pure

The function which gets a character then immediately considers is a pure value. While I called it the most important function, it's clear that right now GetC is pretty useless. All we can possibly do is run getc followed by io... to get an effect totally equivalent to getChar!

io getc        ===     getChar     :: IO Char

But we'll build up from here.


As stated at the beginning, the Functor typeclass provides a function exactly like you're looking for called fmap.

class Functor f where
  fmap :: (a -> b) -> f a -> f b

It turns out that we can instantiate GetC as a Functor so let's do that.

instance Functor GetC where
  fmap f (Pure a)  = Pure (f a)
  fmap f (GetC go) = GetC (\char -> fmap f (go char))

If you squint, you'll notice that fmap affects the Pure constructor only. In the GetC constructor it just gets "pushed down" and deferred until later. This is a hint as to the weakness of fmap, but let's try it.

io                       getc  :: IO Char
io (fmap ord             getc) :: IO Int
io (fmap (\c -> ord + 1) getc) :: IO Int

We've gotten the ability to modify the return type of our IO interpretation of our type, but that's about it! In particular, we're still limited to getting a single character and then running back to IO to do anything interesting with it.

This is the weakness of Functor. Since, as you noted, it deals only with pure functions it gets stuck "at the end of a computation" modifying the Pure constructor only.


The next step is Applicative which extends Functor like this

class Functor f => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

In other words it extends the notion of injecting pure values into our context and allowing pure function application to cross over the data type. Unsurprisingly, GetC instantiates Applicative too

instance Applicative GetC where
  pure = Pure
  Pure f   <*> Pure x   = Pure (f x)
  GetC gof <*> getcx    = GetC (\char -> gof <*> getcx)
  Pure f   <*> GetC gox = GetC (\char -> fmap f (gox char))

Applicative allows us to sequence operations and that might be clear from the definition already. In fact, we can see that (<*>) pushes character application forward so that the GetC actions on either side of (<*>) get performed in order. We use Applicative like this

fmap (,) getc <*> getc :: GetC (Char, Char)

and it allows us to build incredibly interesting functions, much more complex than just Functor would. For instance, we can already form a loop and get an infinite stream of characters.

getAll :: GetC [Char]
getAll = fmap (:) getc <*> getAll

which demonstrates the nature of Applicative being able to sequence actions one after another.

The problem is that we can't stop. io getAll is an infinite loop because it just consumes characters forever. We can't tell it to stop when it sees '\n', for instance, because Applicatives sequence without noticing earlier results.


So let's go the final step an instantiate Monad

instance Monad GetC where
  return = pure
  Pure a  >>= f = f a
  GetC go >>= f = GetC (\char -> go char >>= f)

Which allows us immediately to implement a stopping getAll

getLn :: GetC String
getLn = getc >>= \c -> case c of
  '\n' -> return []
  s    -> fmap (s:) getLn

Or, using do notation

getLn :: GetC String
getLn = do
  c <- getc
  case c of
    '\n' -> return []
    s    -> fmap (s:) getLn

So what gives? Why can we now write a stopping loop?

Because (>>=) :: m a -> (a -> m b) -> m b lets the second argument, a function of the pure value, choose the next action, m b. In this case, if the incoming character is '\n' we choose to return [] and terminate the loop. If not, we choose to recurse.

So that's why you might want a Monad over a Functor. There's much more to the story, but those are the basics.

这篇关于在&gt;&gt; = Monad运算符的签名上的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆