为什么haskell 的bind 函数会从non-monadic 到monadic 取函数 [英] Why does haskell's bind function take a function from non-monadic to monadic

查看:21
本文介绍了为什么haskell 的bind 函数会从non-monadic 到monadic 取函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have some questions about the definition of the binding function (>>=) in Haskell.

因为 Haskell 是纯语言,所以我们可以使用 Monad 来处理有副作用的操作.我觉得这个策略有点像把所有可能导致副作用的动作放到另一个世界,我们可以通过 do>>=do 从我们的纯"haskell 世界控制它们代码>.

Because Haskell is a pure language, so we can use Monad to handle operations with side effects. I think this strategy is somewhat like putting all actions may cause side effects to another world, and we can control them from our "pure" haskell world though do or >>=.

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

So when I look at definition of >>= function

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

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

it takes a (a -> m b) function, so result m a of the former action can be "unpack" to a non-monadic a in >>=. Then the function (a -> m b) takes a as its input and return another monadic m b as its result. By the binding function I can operate on monadic without bringing any side effects into pure haskell codes.

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

My question is why we use a (a -> m b) function? In my opinion, a m a -> m b function can also do this. Is there any reason, or just because it is designed like this?

编辑

从评论中我知道很难从 m a 中提取 a.但是,我认为我可以将 monadic m a 视为具有副作用的 a.

From comments I understand it's hard to extract a from m a. However, I think I can consider a monadic m a as a a with side effect.

是否可以假设函数 m a ->m b 的作用类似于 a ->b,所以我们可以定义 m a ->m b 就像定义 a ->b?

Is it possible to assume function m a -> m b acts similar with a -> b, so we can define m a -> m b like defining a -> b?

推荐答案

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

edit2: OK, here's what I should've said from the start:

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

E as in embedded domain-specific languages. Embedded means that the language's statements are plain values in our language, Haskell.

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

Let's try to have us an IO-language. Imagine we have print1 :: IO () primitive, describing an action of printing an integer 1 at the prompt. Imagine we also have print2 :: IO (). Both are plain Haskell values. In Haskell, we speak of these actions. This IO-language still needs to be interpreted / acted upon by some part of the run-time system later, at "run"-time. Having two languages, we have two worlds, two timelines.

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

We could write do { print1 ; print2 } to describe compound actions. But we can't create a new primitive for printing 3 at the prompt, as it is outside our pure Haskell world. What we have here is an EDSL, but evidently not a very powerful one. We must have an infinite supply of primitives here; not a winning proposition. And it is not even a Functor, as we can't modify these values.

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

Now, what if we could? We'd then be able to tell do { print1 ; print2 ; fmap (1+) print2 }, to print out 3 as well. Now it's a Functor. More powerful, still not flexible enough.

我们可以灵活地构造这些动作描述符(如print1)的原语.它是例如print :: 显示一个 =>->IO 一个.我们现在可以讨论更通用的操作,比如 do { print 42;获取线;putStrLn ("你好," ++ "...你!") }.

We get flexibility with primitives for constructing these action descriptors (like print1). It is e.g. print :: Show a => a -> IO a. We can now talk about more versatile actions, like do { print 42; getLine ; putStrLn ("Hello, " ++ "... you!") }.

但现在我们看到需要参考之前操作的结果".我们希望能够编写 do { print 42;s <- getLine ;putStrLn ("Hello, " ++ s ++ "!") }.我们想根据之前的 IO-actions 的结果(在 Haskell 世界中)创建(在 Haskell 世界中)新的动作描述(Haskell 值描述在 IO-world 中的动作)这些IO动作产生,什么时候运行,什么时候IO-language 被解释(它描述的动作在 IO-world 中执行).

But now we see the need to refer to the "results" of previous actions. We want to be able to write do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") }. We want to create (in Haskell world) new action descriptions (Haskell values describing actions in IO-world) based on results (in Haskell world) of previous IO-actions that these IO-actions will produce, when they are run, when the IO-language is interpreted (the actions it describes been carried out in IO-world).

这意味着能够从 Haskell 值创建那些 IO 语言语句,例如 print :: a ->IO 一个.这正是您要问的类型,而这正是 使该 EDSL 成为 Monad 的原因.

This means the ability to create those IO-language statements from Haskell values, like with print :: a -> IO a. And that is exactly the type you're asking about, and it is what makes this EDSL a Monad.

假设我们有一个 IO 原语 (a_primitive :: IO Int -> IO ()),它按原样打印任何正整数,并打印 "---" 在打印任何非正整数之前在单独的行上.然后我们可以按照您的建议编写 a_primitive (return 1).

Imagine we have an IO primitive (a_primitive :: IO Int -> IO ()) which prints any positive integer as is, and prints "---" on a separate line before printing any non-positive integer. Then we could write a_primitive (return 1), as you suggest.

但是IO是关闭的;它是不纯的;我们不能在 Haskell 中编写新的 IO 原语,也不能为每个可能进入我们脑海的新想法定义一个原语.所以我们写 (x -> if x > 0 then print x else do { putStrln "---"; print x }) 代替,这个 lambda 表达式的类型是 Int->IO()(或多或少).

But IO is closed; it is impure; we can't write new IO primitives in Haskell, and there can't be a primitive already defined for every new idea that might come into our minds. So we write (x -> if x > 0 then print x else do { putStrln "---"; print x }) instead, and that lambda expression's type is Int -> IO () (more or less).

如果上述 lambda 表达式中的参数 xIO Int 类型,则表达式 x >0 会被打错.如果不使用标准的 >>= 运算符(或其等效运算符),就无法从 IO a 中获取 a.

If the argument x in the above lambda-expression were of type IO Int the expression x > 0 would be mistyped. There is no way to get that a out of IO a without the use of the standard >>= operator (or its equivalent).

另见:

而且,这个引用:

有人在某个时候注意到,哦,为了获得不纯的效果从纯代码我需要做元编程,这意味着我的一个类型必须是'计算X'的程序.我想拿一个'计算X'的程序和一个接受X和的函数产生下一个程序,一个'计算Y'的程序,然后以某种方式将它们粘合到一个 ' 程序中,该程序计算一个 Y' "(这是bind 操作).IO monad 诞生了."

"Someone at some point noticed, "oh, in order to get impure effects from pure code I need to do metaprogramming, which means one of my types needs to be 'programs which compute an X'. I want to take a 'program that computes an X' and a function which takes an X and produces the next program, a 'program that computes a Y', and somehow glue them together into a 'program which computes a Y' " (which is the bind operation). The IO monad was born."

<小时>

这些是四种类型的通用函数应用:


edit: These are the four types of generalized function application:

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

这里是相应的类型推导规则,为了清楚起见,参数顺序颠倒了,

And here are the corresponding type derivation rules, with the flipped arguments order for clarity,

 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 中编写新的计算.

Why? They just are. Your question is really, why do we need Monads? Why Functors or Applicative Functors aren't enough? And this was surely already asked and answered many times (e.g., the 2nd link in the list just above). For one, as I tried to show above, monads let us code new computations in Haskell.

这篇关于为什么haskell 的bind 函数会从non-monadic 到monadic 取函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆