为什么约束其价值的责任?

时间:2014-11-10 14:48:52

标签: haskell functional-programming monads

典型的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>

5 个答案:

答案 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 ~ Stringb ~ (),但在其正文中的任何位置都没有调用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

事实上,joinfmap只能使用bind(和return)编写,所以在实践中,只更新一个函数{{1}更容易},而不是两个bindjoin,尽管编写过滤器通常更简单。

基本上,fmapbindfmap的混合。

答案 2 :(得分:4)

  

据我了解(我可能错了),函数(a -> m b)只是从一个结构a到另一个b的映射函数

你对此非常正确 - 如果你改变了#34;映射&#34;到态射。函数a -> m b是monad morphismsKleisli 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 StringIO monad提供的不透明操作,无法根据return>>=定义。

以这种方式思考:最终,Monad永远不会是你自己使用的东西;它是一个用于将外部内容插入其中的界面,例如getLineputStrLn