我对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
?
答案 0 :(得分:6)
edit2:好的,这是我从一开始就应该说的:
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 中编写新的计算。