繁体   English   中英

为什么haskell的bind函数会从非monadic函数到monadic函数

[英]Why does haskell's bind function take a function from non-monadic to monadic

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

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

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

(>>=) :: Monad m => m a -> (a -> m b) -> m b

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

我的问题是为什么我们使用(a -> mb)函数? 在我看来, ma -> mb函数也可以做到这一点。 有什么原因,或者只是因为它是这样设计的?

编辑

从评论中我明白很难从ma提取a 不过,我想我可以考虑一个单子ma作为a有副作用。

是否可以假设函数ma -> mb作用类似于a -> b ,所以我们可以像定义a -> b一样定义ma -> mb

编辑2:好的,这就是我从一开始就应该说的:

Monad 是 EDSL,

E嵌入式领域特定语言中。 嵌入意味着语言的语句在我们的语言 Haskell 中是普通值。

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

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

现在,如果我们可以呢? 然后我们就可以告诉do { print1 ; print2 ; fmap (1+) print2 } do { print1 ; print2 ; fmap (1+) print2 } do { print1 ; print2 ; fmap (1+) print2 } ,同样打印出3 现在它是一个函子。 更强大,仍然不够灵活。

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

但是现在我们看到需要参考之前操作的“结果” 我们希望能够写do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") } do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") } do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") } . 我们希望根据以往的IO -actions的结果(在Haskell世界),这些IO -actions产生,它们运行以创建(在Haskell世界)的新的行动的说明(哈斯克尔值描述IO -world动作),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 表达式中的参数xIO Int类型,则表达式x > 0将被错误键入。 如果不使用标准的>>=运算符(或其等效运算符),则无法从IO a获取a

另见:

而且,这句话

“有人在某个时候注意到,”哦,为了从纯代码中获得不纯的效果,我需要进行元编程,这意味着我的类型之一需要是'计算 X 的程序' 我想要一个‘计算 X 的程序’和一个接受 X 并生成下一个程序的函数,一个‘计算 Y 的程序’ ,然后以某种方式将它们粘在一起形成一个‘计算 Y 的程序’ ”(这是bind操作)。IO monad 诞生了。”


编辑:这些是四种类型的广义函数应用:

( $ ) ::                     (a ->   b) ->   a ->   b     -- plain
(<$>) :: Functor f     =>    (a ->   b) -> f a -> f b     -- functorial
(<*>) :: Applicative f =>  f (a ->   b) -> f a -> f b     -- applicative
(=<<) :: Monad f       =>    (a -> f b) -> f a -> f b     -- monadic

这是相应的类型推导规则,为了清楚起见,使用翻转的参数顺序,

 a                f a                f  a                f a
 a -> b             a -> b           f (a -> b)            a -> f b
 ------           --------           ----------          ----------
      b           f      b           f       b           f        b

 no `f`s          one `f`            two `f`s,           two `f`s: 
                                     both known          one known,
                                                         one constructed

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM