Try to understand the ReaderT instance.
withRunInIO inner =
ReaderT $ \r ->
withRunInIO $ \run ->
inner (run . flip runReaderT r)ris the environment.- Why is there a 2nd
withRunInIo? - What's
run?
According to the docs there's only one method in the type class
The type class has only one method -- askUnliftIO:
even though there are two: askUnliftIO and withRunInIO. It seems they don't consider the latter a distinct method, rather it's just a convenience wrapper around askUnliftIO. At least that's my hypothesis.
So let's focus on askUnliftIO. Here's how it can be used.
(u :: UnliftIO m) <- askUnliftIO
liftIO $ System.Timeout.timeout x $ unliftIO u yIt gives us an unlifted thing (a newtype record). We get the content of the record through unliftIO u. The result of that is a function m a -> IO a. In the above example, we call the function to get the IO a and then that's passed to timeout and ultimately liftIO. So... how?
It's actually really easy! You just look at the source, where askUnliftIO is defined as a really straight forward... nope this is Haskell.
askUnliftIO uses withRunInIO. Talk about single type class method. Seriously I should have known. Never get your hopes up with Haskell. Never.
askUnliftIO :: m (UnliftIO m)
askUnliftIO = withRunInIO (\run -> return (UnliftIO run))Maybe I can make things easier by renaming and making up fantasy syntax.
-- Without the newtype
askUnliftIO :: monad (monad a -> IO a)askUnliftIO basically just gives us a function.
The default implementation (at least I think that's what it is?) is actually understandable now.
withRunInIO inner = askUnliftIO >>= \u -> liftIO (inner (unliftIO u))Get the unlift newtype thingie u, get the function from inside that thing unliftIO u, then pass that to inner (which is (m a -> IO a) -> IO b). It now needs only the IO b part to give you the m b but something something happened in IO or whatever.
ReaderT now.
withRunInIO func =
IdentityT $
withRunInIO $ \run ->
func (run . runIdentityT)The whole
\run -> func (run . runIdentityT)is the inner in
withRunInIO inner =
askUnliftIO >>=
\u -> liftIO (
inner (unliftIO u)
)withRunInIO =
askUnliftIO >>=
\u -> liftIO (
(\run -> func (run . runIdentityT)) (unliftIO u)
)
-- | `func` here is kind of undefined because I just copy pasted one snippet into another. In the real world `func` would be something concrete.So the run function is always (?) the function we get out of our unlift thingie! Cool... ? Sounds like a profound realization.