[英]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:好的,这就是我从一开始就应该说的:
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 表达式中的参数x
是IO 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:另见:
"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.