将ReaderT和runReaderT与SQLite一起使用



我有下面的代码,取自这里:

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)readerT在CCD_ 2中的确切作用?

2)runReaderT到底在做什么?

3)ask函数是如何工作的?

有人有直接的解释吗?这是我第一次使用Readermonad。

1)在本例中,ReaderT的目的是使SQLiteHandle类型的值可用于函数,而无需为每个函数添加额外的参数。

2)runReaderT"打开"ReaderT:newtype 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仅通过将"环境"r(额外参数)封装在底层monad中来提供对其的访问。

这里有一个非常简单的例子(无可否认,太简单而不现实)。

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的函数,它与data Blog a0类似,但也需要一个函数来应用于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函数,它很酷,但我用得不多。

最新更新