Haskell 关于>>;=单子算子
这是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) 注意。我解释了“实际”和“可能”的
>>= :: 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
。如果您决定定义一个monadm
,则需要定义新的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)