的一个关键能力Monad是“查看内部”m a类型并查看a; 但是 的一个关键限制Monad是 monad 必须是“不可避免的”,即Monadtypeclass 操作不应该足以编写 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一起给你这种能力。 (>>=)一般可以写成fmapand 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 的基本函数,但您可以使用这两个函数定义无数其他更高级的函数,并且该模块收集了几十个更常见的函数。您的代码不应被(>>=); 它是一个重要的构建块,既可以单独使用,也可以作为更大构建块的组件。