简体   繁体   English

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

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

I have some questions about the definition of the binding function (>>=) in Haskell.我对 Haskell 中绑定函数(>>=)的定义有一些疑问。

Because Haskell is a pure language, so we can use Monad to handle operations with side effects.因为 Haskell 是纯语言,所以我们可以使用 Monad 来处理有副作用的操作。 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 >>= .我认为这种策略有点像将所有操作都可能对另一个世界造成副作用,我们可以通过do>>=从我们的“纯”haskell 世界控制它们。

So when I look at definition of >>= function所以当我查看>>=函数的定义时

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

it takes a (a -> mb) function, so result ma of the former action can be "unpack" to a non-monadic a in >>= .它需要一个(a -> mb)函数,因此前一个操作的结果ma可以“解包”到>>=的非一元a Then the function (a -> mb) takes a as its input and return another monadic mb as its result.然后函数(a -> mb)a作为其输入并返回另一个 monadic mb作为其结果。 By the binding function I can operate on monadic without bringing any side effects into pure haskell codes.通过绑定函数,我可以对 monadic 进行操作,而不会给纯 Haskell 代码带来任何副作用。

My question is why we use a (a -> mb) function?我的问题是为什么我们使用(a -> mb)函数? In my opinion, a ma -> mb function can also do this.在我看来, ma -> mb函数也可以做到这一点。 Is there any reason, or just because it is designed like this?有什么原因,或者只是因为它是这样设计的?

EDIT编辑

From comments I understand it's hard to extract a from ma .从评论中我明白很难从ma提取a However, I think I can consider a monadic ma as a a with side effect.不过,我想我可以考虑一个单子ma作为a有副作用。

Is it possible to assume function ma -> mb acts similar with a -> b , so we can define ma -> mb like defining a -> b ?是否可以假设函数ma -> mb作用类似于a -> b ,所以我们可以像定义a -> b一样定义ma -> mb

edit2: OK, here's what I should've said from the start:编辑2:好的,这就是我从一开始就应该说的:

Monads are EDSLs, Monad 是 EDSL,

E as in embedded domain-specific languages. E嵌入式领域特定语言中。 Embedded means that the language's statements are plain values in our language, Haskell.嵌入意味着语言的语句在我们的语言 Haskell 中是普通值。

Let's try to have us an IO -language.让我们尝试使用IO语言。 Imagine we have print1 :: IO () primitive, describing an action of printing an integer 1 at the prompt.想象一下,我们有print1 :: IO ()原语,描述了在提示符下打印整数1的操作。 Imagine we also have print2 :: IO () .想象一下,我们还有print2 :: IO () Both are plain Haskell values.两者都是普通的 Haskell 值。 In Haskell, we speak of these actions.在 Haskell 中,我们谈到了这些操作。 This IO -language still needs to be interpreted / acted upon by some part of the run-time system later , at "run"-time.稍后,在“运行”时,此IO语言仍需要由运行时系统的某些部分进行解释/处理。 Having two languages, we have two worlds, two timelines.两种语言,我们就有两个世界,两个时间线。

We could write do { print1 ; print2 }我们可以写do { print1 ; print2 } do { print1 ; print2 } to describe compound actions. do { print1 ; print2 }来描述复合动作。 But we can't create a new primitive for printing 3 at the prompt, as it is outside our pure Haskell world.但是我们不能在提示符下创建一个新的原语来打印3 ,因为它超出了我们纯粹的Haskell 世界。 What we have here is an EDSL, but evidently not a very powerful one.我们这里有一个 EDSL,但显然不是一个非常强大的。 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.它甚至不是 Functor,因为我们无法修改这些值。

Now, what if we could?现在,如果我们可以呢? We'd then be able to tell do { print1 ; print2 ; fmap (1+) print2 }然后我们就可以告诉do { print1 ; print2 ; fmap (1+) print2 } do { print1 ; print2 ; fmap (1+) print2 } do { print1 ; print2 ; fmap (1+) print2 } , to print out 3 as well. do { print1 ; print2 ; fmap (1+) print2 } ,同样打印出3 Now it's a Functor.现在它是一个函子。 More powerful, still not flexible enough.更强大,仍然不够灵活。

We get flexibility with primitives for constructing these action descriptors (like print1 ).我们可以灵活地使用原语来构建这些动作描述符(如print1 )。 It is eg print :: Show a => a -> IO a .例如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; getLine ; putStrLn ("Hello, " ++ "... you!") } do { print 42; getLine ; putStrLn ("Hello, " ++ "... you!") } do { print 42; getLine ; putStrLn ("Hello, " ++ "... you!") } . do { print 42; getLine ; putStrLn ("Hello, " ++ "... you!") } .

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 ++ "!") }我们希望能够写do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") } do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") } do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") } . 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).我们希望根据以往的IO -actions的结果(在Haskell世界),这些IO -actions产生,它们运行以创建(在Haskell世界)的新的行动的说明(哈斯克尔值描述IO -world动作),IO -language 被解释(它描述的操作是在IO -world 中执行的)。

This means the ability to create those IO -language statements from Haskell values, like with print :: a -> IO a .这意味着能够从 Haskell 值创建那些IO语言语句,例如使用print :: a -> IO a And that is exactly the type you're asking about, and it is what makes this EDSL a Monad .这正是您要问的类型,这就是使此 EDSL 成为Monad 的原因


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.想象一下,我们有一个 IO 原语( a_primitive :: IO Int -> IO () ),它按原样打印任何正整数,并在打印任何非正整数之前在单独的行上打印"---" Then we could write a_primitive (return 1) , as you suggest.然后我们可以按照您的建议编写a_primitive (return 1)

But IO is closed;但是IO是关闭的; 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.我们无法在 Haskell 中编写新的 IO 原语,并且不可能为每个可能进入我们脑海的新想法定义一个原语。 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).所以我们写(\\x -> if x > 0 then print x else do { putStrln "---"; print x })代替,并且该 lambda 表达式的类型是Int -> IO () (或多或少)。

If the argument x in the above lambda-expression were of type IO Int the expression x > 0 would be mistyped.如果上述 lambda 表达式中的参数xIO Int类型,则表达式x > 0将被错误键入。 There is no way to get that a out of IO a without the use of the standard >>= operator (or its equivalent).如果不使用标准的>>=运算符(或其等效运算符),则无法从IO a获取a

see also:另见:

And, this quote :而且,这句话

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


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

Why?为什么? They just are.他们只是。 Your question is really, why do we need Monads?你的问题真的是,为什么我们需要 Monads? Why Functors or Applicative Functors aren't enough?为什么 Functors 或 Applicative Functors 还不够? And this was surely already asked and answered many times (eg, the 2nd link in the list just above).这肯定已经被多次询问和回答(例如,上面列表中的第二个链接)。 For one, as I tried to show above, monads let us code new computations in Haskell .一方面,正如我在上面试图展示的那样,monads 让我们可以在 Haskell 中编写新的计算代码。

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

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