Haskell 关于>>;=单子算子

Haskell 关于>>;=单子算子,haskell,monads,Haskell,Monads,这是Haskell中众所周知的>>=运算符的签名 >>= :: Monad m => m a -> (a -> m b) -> m b 问题是为什么函数的类型是 (a -> m b) 而不是 (a -> b) 我想说,后一种方法更实用,因为它允许在所定义的monad中直接集成现有的“纯”函数 相反,编写一个通用的“适配器”似乎并不困难 但无论如何,我认为更可能的是你已经有了(a->b),而不是(a->mb) 注意。我解释了“实际”和“可能”的

这是Haskell中众所周知的>>=运算符的签名

>>= :: Monad m => m a -> (a -> m b) -> m b
问题是为什么函数的类型是

(a -> m b)
而不是

(a -> b)
我想说,后一种方法更实用,因为它允许在所定义的monad中直接集成现有的“纯”函数

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

但无论如何,我认为更可能的是你已经有了
(a->b)
,而不是
(a->mb)

注意。我解释了“实际”和“可能”的含义。 如果您尚未在程序中定义任何monad,那么您拥有的函数是“纯”的
(a->b)
,并且您将拥有0个
(a->mb)
类型的函数,因为您尚未定义
m
。如果您决定定义一个monad
m
,则需要定义新的
a->mb
函数。

原因是
(>=)
更一般。您建议的函数已被调用,可以很容易地定义为

liftM :: (Monad m) => (a -> b) -> (m a -> m b)
liftM f k  =  k >>= return . f
adapt :: (Monad m) => (a -> b) -> (a -> m b)
adapt f = return . f
这个概念有自己的类型类,名为
Functor
,带有
fmap::(Functor m)=>(a->b)->(ma->mb)
。每个
Monad
也是一个带有
fmap=liftM
Functor
,但由于历史原因,在类型类层次结构中没有()捕获它

您建议的
adapt
可以定义为

liftM :: (Monad m) => (a -> b) -> (m a -> m b)
liftM f k  =  k >>= return . f
adapt :: (Monad m) => (a -> b) -> (a -> m b)
adapt f = return . f
请注意,拥有
adapt
相当于拥有
return
,因为
return
可以定义为
adapt id

因此,任何具有
>=
的函数也可以具有这两个函数,但反之亦然

这种差异背后的直觉很简单:一个单子中的计算可以依赖于以前的单子的结果。重要的部分是
(a->mb)
,这意味着不仅
b
,而且它的“效果”
mb
可以依赖于
a
。例如,我们可以

import Control.Monad

mIfThenElse :: (Monad m) => m Bool -> m a -> m a -> m a
mIfThenElse p t f = p >>= \x -> if x then t else f

但是,仅使用
fmap
,不可能仅使用
函子m
约束来定义此函数。函子只允许我们更改“内部”的值,但我们不能将其“外部”以决定要采取的操作。

正如其他人所说,您的绑定是
函子
类的
fmap
函数,也称

但为什么它的功能不如
>=

编写一个通用的“适配器”似乎并不困难

您确实可以使用以下类型编写函数:

adapt f x = return (f x)
但是,此函数不能完成我们希望
>=
的参数执行的所有操作。有一些有用的值是
adapt
无法产生的

在列表monad中,
返回x=[x]
,因此
adapt
将始终返回单个元素列表

Maybe
monad中,
return x=Some x
,因此
adapt
将永远不会返回
None

IO
monad中,一旦检索到一个操作的结果,您所能做的就是从中计算一个新值,您就不能运行后续操作了

所以简而言之,
fmap
>=
能做的事情更少。这并不意味着它是无用的——如果它是:)它就没有名字了,但它的功能不那么强大。

基本上,
(>=)
允许您对操作进行排序,以便后面的操作可以根据早期的结果选择不同的行为。像您要求的更纯粹的函数可以在
Functor
类型类中使用,并且可以使用
(>>=)
进行派生,但是如果您仅使用它,您将无法再对操作进行排序。还有一个中间名为
Applicative
,它允许您对操作进行排序,但不能根据中间结果对其进行更改

例如,让我们构建一个简单的IO操作类型,从Functor到Application再到Monad


我们将关注一个类型
GetC
,如下所示

GetC a = Pure a | GetC (Char -> GetC a)
第一个构造函数在时间上有意义,但第二个构造函数应该立即有意义-
GetC
包含一个可以响应传入字符的函数。我们可以将
GetC
转换为
IO
操作,以提供这些字符

io :: GetC a -> IO a
io (Pure a)  = return a
io (GetC go) = getChar >>= (\char -> io (go char))
这就清楚了
Pure
的来源——它处理我们类型中的纯值。最后,我们将把
GetC
抽象化:我们将禁止直接使用
Pure
GetC
,只允许用户访问我们定义的函数。我现在就写最重要的

getc :: GetC Char
getc = GetC Pure
得到一个字符后立即考虑的函数是一个纯值。虽然我称之为最重要的函数,但很明显,现在
GetC
是非常无用的。我们所能做的就是运行
getc
,然后运行
io
。。。获得完全等同于
getChar
的效果

io getc        ===     getChar     :: IO Char
但我们将从这里开始建设


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

class Functor f where
  fmap :: (a -> b) -> f a -> f b
事实证明,我们可以将
GetC
实例化为
Functor
,让我们这样做吧

instance Functor GetC where
  fmap f (Pure a)  = Pure (f a)
  fmap f (GetC go) = GetC (\char -> fmap f (go char))
如果斜视,您会注意到
fmap
仅影响
Pure
构造函数。在
GetC
构造函数中,它只是被“下推”并推迟到以后。这是关于
fmap
的弱点的提示,但让我们试试看

io                       getc  :: IO Char
io (fmap ord             getc) :: IO Int
io (fmap (\c -> ord + 1) getc) :: IO Int
我们已经能够修改类型的
IO
解释的返回类型,但仅此而已!特别是,我们仍然局限于获取一个字符,然后返回到
IO
,对其执行任何有趣的操作

这是
函子的弱点。因为,正如您所指出的,它只处理它“停留在”的纯函数
class Functor f => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
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))
fmap (,) getc <*> getc :: GetC (Char, Char)
getAll :: GetC [Char]
getAll = fmap (:) getc <*> getAll
instance Monad GetC where
  return = pure
  Pure a  >>= f = f a
  GetC go >>= f = GetC (\char -> go char >>= f)
getLn :: GetC String
getLn = getc >>= \c -> case c of
  '\n' -> return []
  s    -> fmap (s:) getLn
getLn :: GetC String
getLn = do
  c <- getc
  case c of
    '\n' -> return []
    s    -> fmap (s:) getLn
halve :: Int -> Maybe Int
halve n | even n    = Just (n `div` 2)
        | otherwise = Nothing

return 24 >>= halve >>= halve >>= halve
class Mokad m where  
     returk :: t -> m t  
     (>>==) :: m t1 -> (t1 -> t2) -> m t2
instance Mokad Maybe where
         returk x = Just x
         Nothing >>== f = Nothing
         Just x >>== f = Just (f x) -- ????? always Just ?????
chainK :: Maybe Int -> Maybe Int -> Maybe Int -> Maybe Int
chainK ma mb mc = md 
      where
        md = ma >>== \a -> mb >>== \b -> mc >>== \c -> returk $ a+b+c
chainOK :: Maybe Int -> Maybe Int -> Maybe Int -> Maybe Int
chainOK ma mb mc = md 
      where
        md = ma >>= \a -> mb >>= \b -> mc >>= \c -> return (a+b+c)