re-xyr / cleff

Fast and concise extensible effects

Home Page:https://hackage.haskell.org/package/cleff

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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