在SQLite中使用ReaderT和runReaderT?

时间:2017-01-24 10:25:07

标签: sqlite haskell monads

我的代码来自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")

我正在努力理解

1)readerTdata Blog a的确切角色?

2)究竟runReaderT在这做什么?

3)ask函数如何工作?

有没有人有直截了当的解释?这是我第一次使用Reader monad。

1 个答案:

答案 0 :(得分:2)

1)在此示例中,ReaderT的目的是使函数可以使用类型SQLiteHandle的值,而无需为每个函数添加其他参数。

2)runReaderT“展开”ReaderTnewtype ReaderT r m a = ReaderT { runReaderT :: r -> m a}。正如您所看到的,真正的表示形式是r -> m a:从您提供的类型为r的项目到您认为直接处理的m a的函数。因此,ReaderT并没有真正避免必须将新参数添加到您的函数中;它只是为你隐藏它。

3)runReaderT ask == runReaderT $ ReaderT return == return == r -> m r因此ask只是通过将其包装在基础monad中来提供对“环境”r(额外参数)的访问。

这是一个非常简单的(实际上太简单而不现实)的例子。

type ModeFlag = Int

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

相当于

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

当我开始学习Haskell时,这种技术的实用性并不是很明显。但是,请考虑您有许多配置参数的情况,或者您可能预见到需要尽快添加更多配置参数。向函数添加新参数非常不方便。相反,只需将配置值打包到记录中,然后通过ReaderT在整个应用程序中提供。有一个名为asks的函数与ask类似,但也会将函数应用于r值。这可用于从记录中提取某些字段。

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

文档中还有一些ReaderReaderT的示例(底部为http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader.html),其中包括local功能非常酷但我没有'用得太多了。