的一个关键能力Monad
是“查看内部”m a
类型并查看a
; 但是 的一个关键限制Monad
是 monad 必须是“不可避免的”,即Monad
typeclass 操作不应该足以编写 type 的函数Monad m => m a -> a
。 (>>=) :: Monad m => m a -> (a -> m b) -> m b
给你这个能力。
但实现这一目标的方法不止一种。该类Monad
可以这样定义:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Functor f => Monad m where
return :: a -> m a
join :: m (m a) -> m a
你问为什么我们不能有一个Monad m => m a -> (m a -> m b) -> m b
功能。好吧,鉴于f :: a -> b
,fmap f :: ma -> mb
基本上就是这样。但fmap
它本身并不能让你“向内看”Monad m => m a
但又无法摆脱它。然而join
,fmap
一起给你这种能力。 (>>=)
一般可以写成fmap
and join
:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
ma >>= f = join (fmap f ma)
事实上,Monad
当你想出一个定义时,这是定义实例的一个常见技巧——为你的可能的 monad 编写函数,然后(>>=)
使用.join
(>>=)
好吧,这回答了问题的“它必须是它的方式”的一部分,并带有“否”。但是,为什么会这样呢?
我不能代表 Haskell 的设计者,但我喜欢这样想:在 Haskell monadic 编程中,基本的构建块是这样的动作:
getLine :: IO String
putStrLn :: String -> IO ()
更一般地说,这些基本构建块的类型看起来像Monad m => m a
, Monad m => a -> m b
, Monad m => a -> b -> m c
, ..., Monad m => a -> b -> ... -> m z
。人们非正式地称这些动作。 Monad m => m a
是无参数动作,Monad m => a -> m b
是单参数动作,依此类推。
嗯,(>>=) :: Monad m => m a -> (a -> m b) -> m b
基本上是“连接”两个动作的最简单的功能。 getLine >>= putStrLn
是首先执行的动作,getLine
然后执行putStrLn
传递从执行中获得的结果getLine
。如果你有fmap
和join
没有>>=
,你必须写这个:
join (fmap putStrLn getLine)
更一般地说,(>>=)
它体现了一个很像动作“管道”的概念,因此对于使用 monad 作为一种编程语言来说,它是更有用的运算符。
最后一件事:确保您了解该Control.Monad
模块。虽然return
和(>>=)
是 monad 的基本函数,但您可以使用这两个函数定义无数其他更高级的函数,并且该模块收集了几十个更常见的函数。您的代码不应被(>>=)
; 它是一个重要的构建块,既可以单独使用,也可以作为更大构建块的组件。