为什么嵌套在其他 monad 中的 IO 不会执行?有没有办法强迫他们这样做?
Why will a IO nested in other monads not execute? Is there a way to force them to?
这是我上一个问题的跟进。
该问题的解决方案是删除一些 monad,这样就可以执行 IO 操作。
为什么我需要取消嵌套 monad?有没有办法在不取消嵌套的情况下执行IO?
注意:这是一个假设,而不是关于好的或坏的做法的问题。
也许将 IO
想象成 type IO a = World -> (a, World)
会有所帮助;也就是说,一个函数将计算机的当前状态作为其唯一参数,return 是一个新状态以及一些值 a
。这与 GHC 内部 IO
的实际实现并无太大不同,因此希望我们可以原谅这里类比通信的(卑鄙的)方法。
所以 readFile :: FilePath -> IO String
,例如,变成 readFile :: FilePath -> World -> (a, World)
。
而main :: IO ()
确实是main :: World -> ((), World)
。
然而,这意味着 IO _
类型的值是惰性的。它们只是函数!函数在被赋值之前不能做任何事情;在我们的例子中,函数想要的值是一个 World
对象,我们 无法构造它 。 Haskell 中的 IO 之美在于:我们可以通过使用我们熟悉和喜爱的单子运算符(return、绑定)来构建一个 IO
操作,但它在运行时之前无法执行任何操作传入 World
对象。
这意味着我们构建的任何 IO
未通过 main
线程化的操作都不会执行。
所以,有了foobar :: [Char] -> IO [IO ()]
,我们当然可以观察到return值:
main :: IO ()
main = do
ios <- foobar "string"
print "goodbye"
但直到我们解构 ios
并绑定内部 IO
值,这些操作才会收到他们想要的 World
:
main :: IO ()
main = do
ios <- foobar
ios !! 0
ios !! 1
ios !! 2
...
print "goodbye"
或者,简而言之,
main = do
ios <- foobar
sequence ios
print "goodbye"
希望对您有所帮助。
让我们从一个稍微不同的例子开始。如您所知,String
是 Char
:
的列表
GHCi> :set +t
GHCi> "Mississippi"
"Mississippi"
it :: [Char]
Strings
的列表是Char
的列表;即 [[Char]]
:
GHCi> group "Mississippi"
["M","i","ss","i","ss","i","pp","i"]
it :: [[Char]]
group "Mississippi"
是 [[Char]]
,我不希望它作为 [Char]
处理——那样会破坏使用 group
.[= 的意义37=]
对于大多数用途,IO a
值与任何其他值一样,因此适用类似的考虑。举一个具体的(and realistic)例子,假设我们有一个这种类型的函数...
(KeyCode -> IO ()) -> IO (IO ())
... 为 GUI 中的按键事件注册事件处理程序。这个想法是你用一个 KeyCode -> IO ()
参数调用函数,它指定响应按键应该发生什么,运行 结果 IO (IO ())
这样你选择的 KeyCode -> IO ()
处理程序变为活动状态。 inner IO ()
produced by 然而,IO (IO ())
动作有不同的目的:注销事件处理程序,它意味着在应用程序的后期由您自行决定使用——也许永远不会。在这种情况下,您绝对不希望 运行 内部操作紧跟在外部操作之后!
总而言之,IO (IO a)
是一个 IO
动作,当 运行 时会产生另一个 IO
动作,您可能想要也可能不想 运行 还有。
P.S.: 正如 sheyll 提到的 ,join
可用于展平嵌套的 IO
动作,或任何其他嵌套的 monadic 值。顺便说一句,列表也有一个 Monad
实例。你认为join (group "Mississippi")
会做什么?
嗯……你问的问题:
Why did I need to unnest the monads? Is there a way to execute the IO without unnesting?
好吧,让我尽可能直截了当地说:Unnesting 或 join
ing 我们如何在 Haskell-land,不仅仅是另一个 monad 组合器,它是 神圣的特殊 moand 东西 区分 Monad
s 与 Functor
和 Applicative
!
是的,这完全意味着 Monad
类型 class 可以用 join
方法而不是 [=101] 来设计=]>>=
.
其实unnesting和binding是同一件事的两种不同的视角!
让我破坏剩下的部分 post:
join = (>>= id)
...和:
(ma >>= amb) = join (amb <$> ma)
让我们证明它们相等,证明我们可以使>>=
变成join
,反之亦然。
从 >>=
制作 join
现在好了join = (>>= id)
更详细:
join mmx = do mx <- mmx
x <- mx
return x
然后:
join mmx = do mx <- mmx
mx
现在 bind
又名 >>=
:
join mmx = mmx >>= id
并免费使用部分:
join = (>>= id)
从 join
制作 >>=
现在反过来,更难了,我们需要每个 Monad
也是一个 Functor
这一事实。
记住>>=
actaully 做es(双关语):
ma >>= amb = do a <- ma
amb a
我们知道amb
是a -> m b
类型的函数,思路是用fmap
,里面有(c -> d) -> m c -> m d
,如果我们fmap
amb
我们得到一个表达式 fmap amb
然后 c
变成 a
并且 d
变成 m b
并且 m c -> m d
因此变成 m a -> m (m b)
!
现在我们很激动:m (m b)
只是尖叫join
,我们只需要输入m a
,我们可以正常应用:
ma >>= amb = do mb <- fmap amb ma
mb
这是我们在上一节中看到的:
join mmb = do mb <- mmb
mb
和mmb == fmap amb ma
ma >>= amb == do mb <- fmap amb ma == join (fmap amb ma)
mb
给你。
这是我上一个问题的跟进。
该问题的解决方案是删除一些 monad,这样就可以执行 IO 操作。
为什么我需要取消嵌套 monad?有没有办法在不取消嵌套的情况下执行IO?
注意:这是一个假设,而不是关于好的或坏的做法的问题。
也许将 IO
想象成 type IO a = World -> (a, World)
会有所帮助;也就是说,一个函数将计算机的当前状态作为其唯一参数,return 是一个新状态以及一些值 a
。这与 GHC 内部 IO
的实际实现并无太大不同,因此希望我们可以原谅这里类比通信的(卑鄙的)方法。
所以 readFile :: FilePath -> IO String
,例如,变成 readFile :: FilePath -> World -> (a, World)
。
而main :: IO ()
确实是main :: World -> ((), World)
。
然而,这意味着 IO _
类型的值是惰性的。它们只是函数!函数在被赋值之前不能做任何事情;在我们的例子中,函数想要的值是一个 World
对象,我们 无法构造它 。 Haskell 中的 IO 之美在于:我们可以通过使用我们熟悉和喜爱的单子运算符(return、绑定)来构建一个 IO
操作,但它在运行时之前无法执行任何操作传入 World
对象。
这意味着我们构建的任何 IO
未通过 main
线程化的操作都不会执行。
所以,有了foobar :: [Char] -> IO [IO ()]
,我们当然可以观察到return值:
main :: IO ()
main = do
ios <- foobar "string"
print "goodbye"
但直到我们解构 ios
并绑定内部 IO
值,这些操作才会收到他们想要的 World
:
main :: IO ()
main = do
ios <- foobar
ios !! 0
ios !! 1
ios !! 2
...
print "goodbye"
或者,简而言之,
main = do
ios <- foobar
sequence ios
print "goodbye"
希望对您有所帮助。
让我们从一个稍微不同的例子开始。如您所知,String
是 Char
:
GHCi> :set +t
GHCi> "Mississippi"
"Mississippi"
it :: [Char]
Strings
的列表是Char
的列表;即 [[Char]]
:
GHCi> group "Mississippi"
["M","i","ss","i","ss","i","pp","i"]
it :: [[Char]]
group "Mississippi"
是 [[Char]]
,我不希望它作为 [Char]
处理——那样会破坏使用 group
.[= 的意义37=]
对于大多数用途,IO a
值与任何其他值一样,因此适用类似的考虑。举一个具体的(and realistic)例子,假设我们有一个这种类型的函数...
(KeyCode -> IO ()) -> IO (IO ())
... 为 GUI 中的按键事件注册事件处理程序。这个想法是你用一个 KeyCode -> IO ()
参数调用函数,它指定响应按键应该发生什么,运行 结果 IO (IO ())
这样你选择的 KeyCode -> IO ()
处理程序变为活动状态。 inner IO ()
produced by 然而,IO (IO ())
动作有不同的目的:注销事件处理程序,它意味着在应用程序的后期由您自行决定使用——也许永远不会。在这种情况下,您绝对不希望 运行 内部操作紧跟在外部操作之后!
总而言之,IO (IO a)
是一个 IO
动作,当 运行 时会产生另一个 IO
动作,您可能想要也可能不想 运行 还有。
P.S.: 正如 sheyll 提到的 join
可用于展平嵌套的 IO
动作,或任何其他嵌套的 monadic 值。顺便说一句,列表也有一个 Monad
实例。你认为join (group "Mississippi")
会做什么?
嗯……你问的问题:
Why did I need to unnest the monads? Is there a way to execute the IO without unnesting?
好吧,让我尽可能直截了当地说:Unnesting 或 join
ing 我们如何在 Haskell-land,不仅仅是另一个 monad 组合器,它是 神圣的特殊 moand 东西 区分 Monad
s 与 Functor
和 Applicative
!
是的,这完全意味着 Monad
类型 class 可以用 join
方法而不是 [=101] 来设计=]>>=
.
其实unnesting和binding是同一件事的两种不同的视角!
让我破坏剩下的部分 post:
join = (>>= id)
...和:
(ma >>= amb) = join (amb <$> ma)
让我们证明它们相等,证明我们可以使>>=
变成join
,反之亦然。
从 >>=
制作 join
现在好了join = (>>= id)
更详细:
join mmx = do mx <- mmx
x <- mx
return x
然后:
join mmx = do mx <- mmx
mx
现在 bind
又名 >>=
:
join mmx = mmx >>= id
并免费使用部分:
join = (>>= id)
从 join
制作 >>=
现在反过来,更难了,我们需要每个 Monad
也是一个 Functor
这一事实。
记住>>=
actaully 做es(双关语):
ma >>= amb = do a <- ma
amb a
我们知道amb
是a -> m b
类型的函数,思路是用fmap
,里面有(c -> d) -> m c -> m d
,如果我们fmap
amb
我们得到一个表达式 fmap amb
然后 c
变成 a
并且 d
变成 m b
并且 m c -> m d
因此变成 m a -> m (m b)
!
现在我们很激动:m (m b)
只是尖叫join
,我们只需要输入m a
,我们可以正常应用:
ma >>= amb = do mb <- fmap amb ma
mb
这是我们在上一节中看到的:
join mmb = do mb <- mmb
mb
和mmb == fmap amb ma
ma >>= amb == do mb <- fmap amb ma == join (fmap amb ma)
mb
给你。