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

时间:2017-05-22 15:52:53

标签: haskell monads

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

因为Haskell是纯语言,所以我们可以使用Monad来处理副作用的操作。我认为这种策略有点像将所有行为都引起另一个世界的副作用,我们可以通过do>>=从我们的“纯粹”的haskell世界中控制它们。

所以当我查看>>=函数

的定义时
(>>=) :: Monad m => m a -> (a -> m b) -> m b

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

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

修改

根据评论,我了解很难从a中提取m a。但是,我认为我可以将monadic m a视为具有副作用的a

是否可以假设函数m a -> m b的行为与a -> b类似,因此我们可以定义m a -> m b,如定义a -> b

1 个答案:

答案 0 :(得分:6)

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

Monads是EDSL,

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

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

我们可以写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!") }

但是现在我们看到需要参考之前操作的&#34;结果&#34; 。我们希望能够写do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") }。我们希望基于先前 IO 的结果(在Haskell世界中)创建(在Haskell世界中)新的动作描述(Haskell值描述 IO -world中的动作)这些 IO -actions 生成,运行时 IO -language被解释(它描述的行为是在 IO -world中执行的。)

这意味着能够从Haskell值创建那些 IO - 语言语句,例如print :: a -> IO a。这正是您要问的类型,而 是什么让这个EDSL成为 Monad

想象一下,我们有一个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 ()(或多或少)。

如果上述lambda表达式中的参数x的类型为IO Int,则表达式x > 0将会输入错误,并且无法获取a不使用标准IO a运算符直接从>>=开出。

另见:

而且,这quote

  

&#34;有人在某个时刻注意到了,&#34;哦,为了获得不纯净的效果   从纯代码我需要做元编程,这意味着我的一个   类型需要是计算X&#39; 的程序。我想要一个   &#39;计算X&#39; 的程序和带X和的函数   生成下一个程序,一个&#39;计算Y&#39; 的程序,并以某种方式   将它们粘合在一起的&#39;程序中,该程序计算Y&#39; &#34; (哪一个是   bind操作)。 IO monad诞生了。&#34;

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

($)   ::                     (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
 a                f a                f  a                f a
 a -> b           a   -> b           f (a -> b)            a -> f b
 ------           --------           ----------          ----------
      b           f      b           f       b           f        b

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