为什么嵌套在其他 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"

希望对您有所帮助。

让我们从一个稍微不同的例子开始。如您所知,StringChar:

的列表
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?

好吧,让我尽可能直截了当地说:Unnestingjoining 我们如何在 Haskell-land,不仅仅是另一个 monad 组合器,它是 神圣的特殊 moand 东西 区分 Monads 与 FunctorApplicative!

是的,这完全意味着 Monad 类型 class 可以用 join 方法而不是 [=101] 来设计=]>>=.

其实unnestingbinding是同一件事的两种不同的视角!

让我破坏剩下的部分 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

我们知道amba -> m b类型的函数,思路是用fmap,里面有(c -> d) -> m c -> m d,如果我们fmapamb 我们得到一个表达式 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

给你。