Try to understand the ReaderT
instance.
withRunInIO inner =
ReaderT $ \r ->
withRunInIO $ \run ->
inner (run . flip runReaderT r)
r
is 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 y
It 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.