Babra Cunningham
Babra Cunningham

Reputation: 2967

Using ReaderT and runReaderT with SQLite?

I've the below code, taken from here:

type Blog a = ReaderT SQLiteHandle IO a
data BlogDBException = BlogDBException String deriving (Show, Typeable)
instance Exception BlogDBException 

run :: Blog a -> IO a
run m = do 
  db <- openConnection "myblog.db"
  runReaderT m db --runReaderT :: ReaderT r m a -> (r -> m a)


sql :: String -> Blog (Either String[[Row Value]])
sql query = do
  db <- ask --ask :: Monad m => ReaderT r m r
  liftIO $ do
    putStrLn query
    execStatement db query


dbQuery :: Blog [Int]
dbQuery = do 
   r <- sql "select UID from UIDS;"
   case r of 
    Right [rows] -> return [fromIntegral uid | [(_, Int uid)] <- rows]
    Left s -> liftIO $ throwIO (BlogDBException s)
    _ -> liftIO $ throwIO (BlogDBException "Invalid result")

I'm trying to understand

1) the exact role of readerT in data Blog a?

2) exactly what is runReaderT doing here?

3) how does the ask function work?

Does anyone have a straightforward explanation? This is my first time working with the Reader monad.

Upvotes: 2

Views: 395

Answers (1)

user2297560
user2297560

Reputation: 2983

1) In this example, the purpose of ReaderT is to make a value of type SQLiteHandle available to the functions without adding an additional parameter to each function.

2) runReaderT "unwraps" the ReaderT: newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a}. As you can see, the real representation is r -> m a: a function from the provided item of type r to the m a that you thought you were dealing with directly. Thus ReaderT isn't really avoiding the fact that a new parameter has to be added to your functions; it's just hiding it for you.

3) runReaderT ask == runReaderT $ ReaderT return == return == r -> m r Thus ask is providing access to the "environment" r (the extra parameter) simply by wrapping it in the underlying monad.

Here's a very simple (admittedly too simple to be realistic) example.

type ModeFlag = Int

g :: ModeFlag -> IO ()
g modeFlag = ... -- take some action based on modeFlag

is equivalent to

h :: ReaderT ModeFlag IO ()
h = do
  modeFlag <- ask
  ... -- take some action based on modeFlag

The utility of this technique wasn't immediately obvious to me when I started learning Haskell. However, consider the case where you have many configuration parameters or you may foresee the need to add more configuration parameters soon. Adding new arguments to functions is very inconvenient. Instead, just pack your configuration values into a record and provide it throughout your application via ReaderT. There's a function called asks that is like ask but also takes a function to apply to the r value. This can be used to extract certain fields from a record.

data Config :: Config { param1 :: Int, param2 :: String, ... other fields }

doStuff :: ReaderT Config IO ()
doStuff = do
  i <- asks param1
  s <- asks param2
  undefined -- do some stuff

There are some more examples of Reader and ReaderT in the documentation (http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader.html at the bottom), including the local function which is pretty cool but I haven't used much.

Upvotes: 3

Related Questions