典型的monad bind
函数具有以下签名:
m a -> (a -> m b) -> m b
据我了解(我可能错了),函数(a -> m b)
只是从一个结构a
到另一个b
的映射函数。假设这是正确的,那么问题为什么bind
的签名不仅仅是:
m a -> (a -> b) -> m b
鉴于unit
是monad定义的一部分;为什么让函数(a -> m b)
有责任在unit
生成的b
上调用bind
,使{{1}}成为其中的一部分是不是更明智?< / p>
答案 0 :(得分:9)
像m a -> (a -> b) -> m b
这样的函数等同于fmap :: (a -> b) -> f a -> f b
。所有fmap
可以做的是更改操作中的值,它无法执行新操作。使用m a -> (a -> m b) -> m b
,您可以&#34;运行&#34; m a
,将该值提供给(a -> m b)
,然后返回m b
的新效果。如果没有这个,你只能在你的程序中产生一个效果,你不能顺序地有两个打印语句,你不能连接到网络然后下载一个URL,您无法响应用户输入,您只能转换每个基本操作返回的值。这个操作允许monad比functor或applicatives更强大。
此处的另一个细节是,您不一定只使用unit
包装值,m b
可以代表某个操作,而不仅仅是返回某些内容。例如,操作return
中对putStrLn :: String -> m ()
的调用在哪里?此函数的签名与>>=
的第二个参数兼容,a ~ String
和b ~ ()
,但在其正文中的任何位置都没有调用return
。 >>=
的要点是将两个动作排在一起,而不仅仅是在上下文中包装值。
答案 1 :(得分:6)
因为m a -> (a -> b) -> m b
只是Monas所拥有的fmap
,因此是仿函数。
Monad添加到仿函数的能力是join
(或压缩)嵌套Monad到简单的Monad的能力。
示例列表到简单列表,或[[1,2], [3]]
到[1,2,3]
。
如果您使用b
签名中的m b
替换fmap
,最终会以
m a -> (a -> m b) -> m (m b)
使用普通仿函数,您会遇到双层容器(m (m b)
)。有了Monad,
使用join
功能,您可以压缩 m (m b)
到m b
。所以bind
实际上是join.fmap
。
事实上,join
和fmap
只能使用bind
(和return
)编写,所以在实践中,只更新一个函数{{1}更容易},而不是两个bind
和join
,尽管编写过滤器通常更简单。
基本上,fmap
是bind
和fmap
的混合。
答案 2 :(得分:4)
据我了解(我可能错了),函数
(a -> m b)
只是从一个结构a
到另一个b
的映射函数
你对此非常正确 - 如果你改变了#34;映射&#34;到态射。函数a -> m b
是monad morphisms的Kleisli category。从这个角度来看,monad的特征是你可以像编写函数一样组成Kleislis:
type Kleisli m a b = a -> m b -- `Control.Arrow` has this as a `newtype` with `Category` instance.
-- compare (.) :: (b->c) -> (a->b) -> a->c
(<=<) :: Kleisli m b c -> Kleisli m a b -> Kleisli m a c
(f<=<g) x = f =<< g x
此外,您可以使用普通函数作为Kleislis:
(return.) :: (a->b) -> Kleisli m a b
但是,Kleislis比功能更强大。例如。对于m ≡ IO
,它们基本上都是可以产生副作用的函数,正如你所知,普通的Haskell函数不能。所以你不能把Kleisli变成一个函数 - 如果>>=
接受了a->b
而不是Kleisli m a b
,但你所拥有的只是一个Kleisli,那就没有了使用它的方式。
答案 3 :(得分:2)
类型a -> m b
的功能可能比类型a -> b
后面跟return
更多的功能(或者你称之为&#34;单位&#34;)。实际上没有&#34;有效的&#34;操作可以用后一种形式表示。
答案 4 :(得分:1)
另一种看法:任何有用的monad都会有一些特定于它的操作,而不仅仅是来自monadic接口的那些操作。例如,IO
monad有getLine :: IO String
。考虑这个非常简单的程序:
main :: IO ()
main = do name <- prompt "What's your name?"
putStrLn ("Hello " ++ name ++ "!")
prompt :: String -> IO String
prompt str = do putStrLn str
getLine
请注意,prompt
的类型符合a -> m b
模式,但它不会在任何地方使用return
(a.k.a。unit
)。这是因为它使用getLine :: IO String
,IO
monad提供的不透明操作,无法根据return
和>>=
定义。
以这种方式思考:最终,Monad
永远不会是你自己使用的东西;它是一个用于将外部内容插入其中的界面,例如getLine
和putStrLn
。