将 ReaderT 和 runReaderT 与 SQLite 一起使用?
Using ReaderT and runReaderT with SQLite?
我有以下代码,取自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) readerT
在data Blog a
中的确切作用?
2) runReaderT
到底在做什么?
3) ask
函数是如何工作的?
有没有人有一个简单的解释?这是我第一次使用 Reader
monad。
1) 在此示例中,ReaderT
的目的是使类型 SQLiteHandle
的值可用于函数,而无需为每个函数添加额外的参数。
2) runReaderT
"unwraps" 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
提供对 "environment" 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
的函数,它类似于 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
文档中还有一些 Reader
和 ReaderT
的示例(http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader.html 在底部),包括非常酷的 local
函数,但我没怎么用过。
我有以下代码,取自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) readerT
在data Blog a
中的确切作用?
2) runReaderT
到底在做什么?
3) ask
函数是如何工作的?
有没有人有一个简单的解释?这是我第一次使用 Reader
monad。
1) 在此示例中,ReaderT
的目的是使类型 SQLiteHandle
的值可用于函数,而无需为每个函数添加额外的参数。
2) runReaderT
"unwraps" 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
提供对 "environment" 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
的函数,它类似于 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
文档中还有一些 Reader
和 ReaderT
的示例(http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader.html 在底部),包括非常酷的 local
函数,但我没怎么用过。