liftIO embeds an IO action in a larger monadic action. Use it when code runs in a context such as ReaderT Config IO or Servant's Handler but still needs IO operations: printing, filesystem access, database queries, or updates. section 15.1.1 section 17.1.3
Its essential type is liftIO :: IO a -> m a, where m implements MonadIO. The result value keeps its type while the action gains the surrounding context. For example, putStrLn produces IO (), while liftIO $ putStrLn ... can be used inside App (). section 15.1.1
Reading configuration and printing paths
An App action can read Config with ask, then lift an IO print action: example :: App () example = do env <- ask let fromPath = configFromPath env toPath = configToPath env liftIO $ putStrLn (fromPath <> " -> " <> toPath) The application keeps access to its ReaderT environment instead of dropping down to plain IO. section 15.1.1
Use liftIO at the boundary where an App or Handler needs an existing IO action:
- Check filesystem state with doesFileExist and getModificationTime while returning an App FileDiff. section 15.2
- Determine a file type, copy a file, or list directory contents while preserving configuration, state, and logging behavior in App. section 15.2
- Run a database query or update in Handler, then handle application-level results such as SQL errors or missing entries. section 17.1.3
liftIO differs from the more general transformer operation lift. lift embeds an action from an arbitrary underlying monad into a transformer; liftIO specifically embeds IO through MonadIO. Prefer liftIO when the action already has type IO a. section 15.1.1
A small Servant handler
A handler can be the lifted database action directly: entriesHandler :: IO [TodoEntry] -> Handler [TodoEntry] entriesHandler = liftIO For a write operation, liftIO runs the database action and the Handler can translate an error into an HTTP error with throwError. section 17.1.3 section 17.1.3
ghci> import Control.Monad.Trans.Reader
ghci> type App a = ReaderT Config IO a
ghci> import Control.Monad.IO.Class (liftIO)
ghci> :{
ghci| example :: App ()
ghci| example = do
ghci| env <- ask
ghci| let toPath = configToPath env
ghci| fromPath = configFromPath env
ghci| in liftIO $ putStrLn (fromPath <> " -> " <> toPath)
ghci| :}
ghci> cfg = Config "src" "dst" Nothing Nothing Nothing
ghci> runReaderT example cfg
src -> dst
Inspect a complete ReaderT Config IO action that combines ask with liftIO and runs it using runReaderT. section 15.1.1
