The mess of `IO`-related HO interpretation
re-xyr opened this issue · comments
Currently there are two functions for IO
-related HO effects:
-- assume `Handling e es esSend =>` in all signatures below
withUnliftIO :: ((Eff esSend ~> IO) -> Eff es a) -> Eff es a
withLiftIO :: ((IO ~> Eff esSend) -> IO a) -> IO a
They are unintuitive as there are slight mismatches between them and standard withRunInIO
and liftIO
functions. This is because, even if structurally Eff
is a type "larger" than IO
, principally it is smaller since IOE
is the "strongest" effect. To maintain both of this we ended up with these weird signatures. withUnliftIO
is useless unless there is a liftIO :: IO ~> Eff es
, and withLiftIO
is useless unless there is an unliftIO :: Eff esSend ~> IO
.
Therefore we conclude that withUnliftIO
depends on IOE :> es
. This is convenient as now we can rewrite withUnliftIO
into a more conventional form:
withToIO :: IOE :> es => ((Eff esSend ~> IO) -> IO a) -> Eff es a
This is also the form of this function in effectful
. For withLiftIO
, on the other hand, it depends on withUnliftIO
and therefore depends on IOE :> es
too. From this point we can define withFromIO :: IOE :> es => ((IO ~> Eff esSend) -> Eff esSend a) -> Eff es a
:
withFromIO :: IOE :> es => ((IO ~> Eff esSend) -> Eff esSend a) -> Eff es a
withFromIO f = withLiftEff \lift -> runThere (f (lift . liftIO))
This is practically equivalent to merely a continuation version of IO ~> Eff esSend
. So we might just, instead, have fromIO
:
fromIO :: IOE :> es => IO ~> Eff esSend
fromIO = Eff . const
So in conclusion, we are able to have:
withToIO :: IOE :> es => ((Eff esSend ~> IO) -> IO a) -> Eff es a -- or `Eff esSend a`
withToIO f = Eff \es -> f \m -> unEff m (Env.update es sendEnv)
fromIO :: IOE :> es => IO ~> Eff esSend
fromIO = Eff . const