liveBook · Manning

liftIO

As explained in Learn Haskell by Example

liftIO embeds an IO action into a monadic context that supports the MonadIO interface.

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

How Learn Haskell by Example covers it

How deep it goes
Defines MonadIO and contrasts liftIO with MonadTrans lift through working ReaderT and Handler implementations.
Examples
Uses path printing, file checks and copying, and Todo database handlers as concrete IO embeddings.
What it assumes
Introduces liftIO through IO actions, ReaderT environments, and Handler signatures without assuming prior transformer use.
What you can do after
Apply it via MonadIO to use existing IO actions inside ReaderT-based applications and Servant handlers.
How it connects
Connects MonadIO to ReaderT, MonadTrans, App, Handler, ask, and Servant.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Haskell by Example. The section links point to the parts of the book it draws on.