a
values, but need to work with type class instances for Either
, Maybe
, and List
. We can think of these as "levels" of wrappers:Either
, then it's pretty straightforward. To access the inner Maybe
, you need to find a way to get into Either
structure, do what you need at that level, and get back out again. You can have all of your functions wrap and unwrap the various layers, but that composes badly and leads to a lot of boilerplate.lift
a function through the layers. Take something that works on one layer, and make it work on another. We can do this manually, but for all of the common data types, there are type classes to help us do this in a straightforward way. By far the most common one is liftIO
.IO
type, which is where the horrible, unpredictable, tainted imperative world touches your beautiful, pristine functional clockwork.IO
constructor (i.e. it's not exported, something you can do in your modules, too), so we want to avoid touching IO
directly as much as possible because it's impossible to get fully out of it. At the end of the day your function will get used in a IO
context (even if you're several layers down). Most of the Haskell style is creating clean, pure, high-level DSLs, and calling those inside IO
.IO
actions occur frequently. Accessing CLI input, talking to the database, and making a network request are all things that you'll want to do on a regular basis. If the monad that you're working in also has a MonadIO
instance, you get access to liftIO
. Fission uses MonadRIO
very frequently, which inherits from MonadIO
. The best way to read liftIO
is as "turn an IO
function into one that happens in my current wrapper."IO
-enabled wrapper: m
. It's done this way to make it match easily with any other MonadIO
instance, rather than using IO directly.RIO
.RIO
directly because it's used while setting up the database pool for our ambient config on app startup, and it's more convenient phrased this way for this scenario.createPool :: IO (Pool SeldaConnection)
needs to be brought into RIO
. Remember that IO
doesn't export a constructor for us to explicitly destructure and repackage! We need a way to turn IO a
into RIO cfg a
.RIO
stands for "Reader
+ IO
." Clearly this has a MonadIO
instance! liftIO
instance for RIO
. The upshot is that it makes IO
actions compatible with the rest of your RIO
function.