这是我上一个问题的后续跟进。 IO action nested in other monads not executing
该问题的解决方案是删除一些monad,并允许执行IO操作。
为什么我需要取消monad?有没有办法在不去除的情况下执行IO?
注意:这是 what-if ,而不是关于好的或坏的做法的问题。
答案 0 :(得分:9)
将IO
视为type IO a = World -> (a, World)
可能会有所帮助;也就是说,一个函数将其唯一参数作为计算机的当前状态,并返回一个新状态以及一些值a
。这与GHC内部的IO
的实际实现并没有太大的不同,所以希望我们可以原谅这种类比的(卑鄙)交流方法。
因此,readFile :: FilePath -> IO String
变为readFile :: FilePath -> World -> (a, World)
。
而main :: IO ()
确实是main :: World -> ((), World)
。
然而,这意味着类型IO _
的值是惰性的。它们只是功能!在给定值之前,函数不能执行任何操作;在我们的例子中,函数需要的值是World
对象,我们无法构造。这就是Haskell中IO的美妙之处:我们可以通过使用我们熟悉和喜欢的monadic运算符(return,bind)来构建IO
动作,但是在运行时传入World
之前它无法执行任何操作对象
这意味着我们构建的任何IO
操作都不会通过main
进行操作。
因此,使用foobar :: [Char] -> IO [IO ()]
,我们当然可以观察到返回值:
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"
希望这有帮助。
答案 1 :(得分:4)
让我们从略有不同的例子开始。如您所知,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
失败。
对于大多数用途,IO a
值与任何其他值一样,因此适用类似的注意事项。为了给出具体的(and realistic)示例,假设我们有一个这种类型的函数......
(KeyCode -> IO ()) -> IO (IO ())
...在GUI中为按键事件注册事件处理程序。我们的想法是,您使用KeyCode -> IO ()
参数调用该函数,该参数指定响应按键时应发生的情况,并运行生成的IO (IO ())
以便您选择的KeyCode -> IO ()
处理程序变得活跃。然而,由 IO ()
操作生成的内部 IO (IO ())
用于不同的目的:它取消注册事件处理程序,它应该是您可以自行决定在申请的稍后时间使用 - 也许永远不会。在这种情况下,你绝对不希望在外部动作之后立即运行内部动作!
总结一下,IO (IO a)
是一个IO
操作,在运行时会产生另一个IO
操作,您可能也可能不想这样做。
P.S。:正如她提到in the other Q&A,join
可以用来展平嵌套的IO
动作或任何其他嵌套的monadic值。顺便提一下,列表也有Monad
个实例。您认为join (group "Mississippi")
会做什么?
答案 2 :(得分:0)
嗯......你问这个问题:
为什么我需要取消monad?有没有办法在不去除的情况下执行IO?
好吧,让我尽可能直言不讳: Unnesting 或 join
我们在Haskell-land中如何称呼它,不仅仅是另一个monad combinator,一个圣洁的特殊事物,将Monad
与Functor
和Applicative
区分开来!
是的,这完全意味着Monad
类型类可以使用 join
方法而不是 >>=
进行设计
实际上不需要和绑定 是同一件事的两个不同观点!
让我破坏这篇文章的其余部分:
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
的事实。
请记住>>=
行为做 es(双关语):
ma >>= amb = do a <- ma
amb a
我们知道amb
是a -> m b
类型的函数,我们的想法是使用fmap
(c -> d) -> m c -> m d
,如果我们fmap
{{1我们得到一个表达式amb
,然后fmap amb
变为c
而a
变为d
,m b
因此变为m c -> m d
!< / p>
现在我们很激动: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
你去。