为什么函数不能获取monadic值并返回另一个monadic值?

pol*_*pts 7 monads haskell functional-programming

假设我们有两个monadic函数:

  f :: a -> m b
  g :: b -> m c
  h :: a -> m c
Run Code Online (Sandbox Code Playgroud)

绑定函数定义为

(>>=) :: m a -> (a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)

我的问题是为什么我们不能做类似下面的事情.声明一个函数,该函数将采用monadic值并返回另一个monadic值?

  f :: a -> m b
  g :: m b -> m c
  h :: a -> m c
Run Code Online (Sandbox Code Playgroud)

绑定函数定义为

(>>=) :: m a -> (ma -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)

haskell中有什么限制函数采用monadic值作为其参数?

编辑:我想我没有说清楚我的问题.关键是,当你使用bind运算符编写函数时,为什么bind运算符的第二个参数是一个采用非monadic值(b)的函数?为什么不能采取monadic值(mb)并回馈mc .是这样的,当你处理monad并且你将要编写的函数将始终具有以下类型.

  f :: a -> m b
  g :: b -> m c
  h :: a -> m c
Run Code Online (Sandbox Code Playgroud)

h = f 'compose' g

我正在努力学习单子,这是我无法理解的.

Lui*_*las 7

一个关键的能力Monad是"向内看" m a类型并看到一个a; 但一个关键的限制Monad是monad必须是"不可避免的",即Monad类型类操作不应该足以编写类型的函数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
Run Code Online (Sandbox Code Playgroud)

你问为什么我们没有Monad m => m a -> (m a -> m b) -> m b功能.嗯,给定f :: a -> b,fmap f :: ma -> mb基本上就是这样.但是fmap,它本身并不能让你"向内看" Monad m => m a但却无法摆脱它.然而join,fmap一起给你这种能力. (>>=)可一般被写入与fmapjoin:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
ma >>= f = join (fmap f ma)
Run Code Online (Sandbox Code Playgroud)

实际上,这是一个常见的技巧,用于定义一个Monad实例,当你遇到定义(>>=)-write join函数为你想成为monad时,然后使用泛型定义(>>=).


嗯,这回答了"它必须是它的方式"问题的一部分与"不".但是,它为什么会这样呢?

我不能代表Haskell的设计者,但我喜欢这样思考:在Haskell monadic编程中,基本构建块是这样的动作:

getLine :: IO String
putStrLn :: String -> IO ()
Run Code Online (Sandbox Code Playgroud)

更一般地,这些基本构造块看起来像类型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)
Run Code Online (Sandbox Code Playgroud)

更一般地说,(>>=)体现了一个很像行为"管道"的概念,因此使用monads作为一种编程语言更有用.


最后一件事:确保您了解该Control.Monad模块.虽然return并且(>>=)是monad的基本功能,但是你可以使用这两个函数来定义其他更多高级函数,并且该模块会收集几十个更常见的函数.你的代码不应该被迫穿上紧身衣(>>=); 它是一个关键的构建块,既可以单独使用,也可以作为较大构建块的组件.


Dan*_*ton 5

为什么我们不能做类似下面的事情.声明一个函数,该函数将采用monadic值并返回另一个monadic值?

f :: a -> m b
g :: m b -> m c
h :: a -> m c
Run Code Online (Sandbox Code Playgroud)

我明白你想写下面的内容吗?

compose :: (a -> m b) -> (m b -> m c) -> (a -> m c)
compose f g = h where
  h = ???
Run Code Online (Sandbox Code Playgroud)

事实证明,这只是常规的函数组合,但参数顺序相反

(.) :: (y -> z) -> (x -> y) -> (x -> z)
(g . f) = \x -> g (f x)
Run Code Online (Sandbox Code Playgroud)

让我们选择专注(.)与类型x = a,y = m b以及z = m c

(.) :: (m b -> m c) -> (a -> m b) -> (a -> m c)
Run Code Online (Sandbox Code Playgroud)

现在翻转输入的顺序,您就可以获得所需的compose功能

compose :: (a -> m b) -> (m b -> m c) -> (a -> m c)
compose = flip (.)
Run Code Online (Sandbox Code Playgroud)

请注意,我们在这里任何地方都没有提到monad.这适用于任何类型的构造函数m,无论它是否为monad.


现在让我们考虑你的另一个问题.假设我们要写以下内容:

composeM :: (a -> m b) -> (b -> m c) -> (a -> m c)
Run Code Online (Sandbox Code Playgroud)

停止.霍格时间.对于那种类型的签名,我们发现有完全匹配!它>=>来自Control.Monad,但请注意,对于此函数,m 必须是monad.

现在问题是为什么.是什么让这个来自组成不同的其它一个,使得一个要求m是一个单子,而其他不?那么,这个问题的答案在于理解Monad抽象是什么的核心,所以我将对谈论这个主题的各种互联网资源留下更详细的答案.我只想说,没有办法写composeM不知道的东西有关m.来吧,试一试.如果没有关于什么m的额外知识,你就无法编写它,而编写这个函数所需的额外知识恰好就是m具有a的结构Monad.