为什么haskell的绑定函数从非monadic到monadic采用函数

cal*_*vin 4 monads haskell

我对(>>=)Haskell中绑定函数的定义有一些疑问.

因为Haskell是一种纯语言,所以我们可以使用Monad来处理带副作用的操作.我认为这个策略是有点像把所有的动作可能会引起副作用另一个世界,我们可以从"纯"哈斯克尔世界虽然控制它们do>>=.

所以当我看一下>>=函数的 定义时

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

它需要一个(a -> m b)函数,因此m a前一个动作的结果可以"解包"到非monadic a>>=.然后,该功能(a -> m b)需要a作为其输入和返回另一个单子m b作为其结果.通过绑定功能,我可以对monadic进行操作,而不会给纯haskell代码带来任何副作用.

我的问题是为什么我们使用一个(a -> m b)函数?在我看来,一个m a -> m b函数也可以做到这一点.有什么理由,还是因为它的设计是这样的?

编辑

从意见,我知道这是很难提取am a.但是,我认为我可以将monadic m a视为a副作用.

是否有可能假设函数m a -> m b行为类似a -> b,所以我们可以定义m a -> m b像定义a -> b

Wil*_*ess 6

edit2:好的,这是我从一开始就应该说的:

Monads是EDSL,

E嵌入式域特定语言一样.嵌入式意味着语言的语句是我们语言中的普通值,Haskell.

让我们尝试让我们使用IO语言.想象一下,我们有print1 :: IO ()原始的,描述了1在提示符下打印整数的动作.想象一下,我们也有print2 :: IO ().两者都是普通的Haskell值.在Haskell中,我们谈到了这些行为.这个IO语言仍然需要稍后在"运行"时间由运行时系统的某些部分进行解释/操作.有两种语言,我们有两个世界,两个时间轴.

我们可以写do { print1 ; print2 }来描述复合动作.但是我们不能3在提示符下创建一个新的原语进行打印,因为它超出了我们纯粹的 Haskell世界.我们这里有一个EDSL,但显然不是一个非常强大的EDSL.我们必须在这里拥有无限的原始供应; 不是一个成功的主张.它甚至不是Functor,因为我们无法修改这些值.

现在,如果可以的话呢?然后我们就可以告诉你do { print1 ; print2 ; fmap (1+) print2 },也可以打印出来3.现在它是一个Functor.更强大,仍然不够灵活.

我们可以灵活地使用原语构建这些动作描述符(比如print1).这是例如print :: Show a => a -> IO a.我们现在可以谈论更多样化的行动,比如do { print 42; getLine ; putStrLn ("Hello, " ++ "... you!") }.

但现在我们看到需要参考之前行动的"结果".我们希望能够写作do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") }.我们要创建(在Haskell世界)的新的行动的说明(哈斯克尔值描述的行为IO基于先前的结果(在Haskell世界)-world)IO -actions这些IO -actions 产生,它们运行,该解释IO语言(它描述的行为是在IO -world中执行的).

这意味着能够从Haskell值创建这些IO语言语句,就像使用print :: a -> IO a.而这正是你问的类型,这什么使这EDSL一个单子.


想象一下,我们有一个IO原语(a_primitive :: IO Int -> IO ())按原样打印任何正整数,并"---"在打印任何非正整数之前打印在单独的行上.然后我们可以a_primitive (return 1)像你建议的那样写.

但IO已关闭; 这是不纯洁的; 我们不能在Haskell中编写新的IO原语,并且不存在已经为可能进入我们脑海中的任何新想法定义的原语.所以我们写了(\x -> if x > 0 then print x else do { putStrln "---"; print x }),而lambda表达式的类型是Int -> IO ()(或多或少).

如果参数x在上述λ-表达均型IO Int,表达x > 0会被拼写错误,并没有办法让a出的IO a直接,不使用标准的>>=运营商.

也可以看看:

而且,这句话:

"有人在某个时候注意到了,"哦,为了从纯代码中获得不纯净的效果,我需要进行元编程,这意味着我的一个类型需要是'计算X的程序'.我想采用一个 "计算X的程序"和一个带X的函数并生成下一个程序,一个'计算Y'的程序,并以某种方式将它们粘合成一个'计算Y'的程序 "(这是 bind操作).IO monad诞生了."


编辑:这是四种类型的应用程序:

($)   ::                     (a ->   b) ->   a ->   b
(<$>) :: Functor f     =>    (a ->   b) -> f a -> f b
(<*>) :: Applicative f =>  f (a ->   b) -> f a -> f b
(=<<) :: Monad f       =>    (a -> f b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

为什么?他们就是.你的问题是,为什么我们需要Monads?为什么Functor或Applicative Functors还不够?这肯定已经被多次询问和回答(例如,上面列表中的第二个链接).首先,正如我试图在上面展示的那样,monads让我们在Haskell中编写新的计算代码.