Why will a IO nested in other monads not execute? Is there a way to force them to?

帅比萌擦擦* 提交于 2019-12-04 03:48:20

问题


This is a follow up of my last question. IO action nested in other monads not executing

The solution to that question was to remove some of the monads, and that allowed the IO action to execute.

Why did I need to unnest the monads? Is there a way to execute the IO without unnesting?

Note: This is a what-if more than it is a question about good or bad practice.


回答1:


Perhaps it would help to think of IO as type IO a = World -> (a, World); that is, a function that takes as its only parameter the current state of your computer and returns a new state along with some value a. This is not too dissimilar from the actual implementation of IO in GHC internals, so hopefully we can forgive the (abject) method of communicating by analogy here.

So readFile :: FilePath -> IO String, for example, becomes readFile :: FilePath -> World -> (a, World).

And main :: IO () is really main :: World -> ((), World).

What this means, however, is that values with type IO _ are inert. They are just functions! Functions cannot do anything until they are given a value; in our case, the value the function wants is a World object, which we have no way of constructing. Therein lies the beauty of IO in Haskell: we can build up an IO action by using the monadic operators we know and love (return, bind) but it cannot do anything until the runtime passes in the World object.

Which means that any IO action we build that isn't threaded through main won't be executed.

So, with foobar :: [Char] -> IO [IO ()], we can certainly observe the return value:

main :: IO ()
main = do
  ios <- foobar "string"
  print "goodbye"

But it's not until we deconstruct ios and bind the internal IO values that those actions receive the World they desire:

main :: IO ()
main = do
  ios <- foobar
  ios !! 0
  ios !! 1
  ios !! 2
  ...
  print "goodbye"

Or, for short,

main = do
  ios <- foobar
  sequence ios
  print "goodbye"

Hope this helps.




回答2:


Let's begin with a slightly different example. As you know, a String is a list of Char:

GHCi> :set +t
GHCi> "Mississippi"
"Mississippi"
it :: [Char]

A list of Strings is a list of lists of Char; that is, a [[Char]]:

GHCi> group "Mississippi"
["M","i","ss","i","ss","i","pp","i"]
it :: [[Char]]

group "Mississippi" is a [[Char]], and I don't want it handled as a [Char] -- that would defeat the point of using group.

An IO a value is, for most purposes, a value just like any other, and so analogous considerations apply. To give a concrete (and realistic) example, suppose we have a function with this type...

(KeyCode -> IO ()) -> IO (IO ())

... which registers event handlers for a key press event in a GUI. The idea is that you call the function with a KeyCode -> IO () argument, which specifies what should happen in response to the key press, and run the the resulting IO (IO ()) so that the your chosen KeyCode -> IO () handler becomes active. The inner IO () produced by the IO (IO ()) action, however, serves a different purpose: it unregisters the event handler, and it is meant to be used at a later point of the application at your discretion -- perhaps never. In this case, you definitely do not want to run the inner action immediately after the outer one!

Summing it up, an IO (IO a) is an IO action that, when run, produces another IO action, which you may or may not want to run as well.

P.S.: As sheyll mentioned in the other Q&A, join can be used to flatten a nested IO action, or any other nested monadic value. Incidentally, lists also have a Monad instance. What do you think join (group "Mississippi") will do?




回答3:


Well ... you ask the question:

Why did I need to unnest the monads? Is there a way to execute the IO without unnesting?

Well, let me put it as blunt as possibe: Unnesting or joining how we call it in Haskell-land, is not just yet another monad combinator, it is a holy special moand thing that differentiates Monads from Functor and Applicative!

Yes, that totally means that the Monad type class could have been designed with a join method instead of >>=.

Actually unnesting and binding are two different perspectives for the same thing!

Let me spoil the rest of this post:

join = (>>= id)                  

... and:

(ma >>= amb) = join (amb <$> ma)

Let's prove them equal by showing that we can make a join of >>= and vice versa.

Making join from >>=

Ok now join = (>>= id) in more detail:

join mmx = do mx <- mmx
              x <- mx
              return x

and then:

join mmx = do mx <- mmx
              mx

and now with bind aka >>=:

join mmx = mmx >>= id

and point free using sections:

join = (>>= id)

Making >>= from join

Now the other way around, it is harder, we need that fact that every Monad is a Functor too.

Remember what >>= actaully does (pun intended):

ma >>= amb = do a <- ma
                amb a

We know that amb is a function of type a -> m b, the idea is to use fmap which has (c -> d) -> m c -> m d, if we fmap amb we get an expression fmap amb then c becomes a and d becomes m b and m c -> m d hence becomes m a -> m (m b)!

Now we are thrilled: m (m b) just screams join, we only need to put in the m a which we can do with normal application:

ma >>= amb = do mb <- fmap amb ma
                mb

and this is something we saw in the previous section:

join mmb = do mb <- mmb
              mb

with mmb == fmap amb ma

ma >>= amb == do mb <- fmap amb ma == join (fmap amb ma)
                 mb

There you go.



来源:https://stackoverflow.com/questions/42404258/why-will-a-io-nested-in-other-monads-not-execute-is-there-a-way-to-force-them-t

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!