将类型与 ExceptT IO monad 转换器对齐
Align types with ExceptT IO monad transformer
试图将我的头脑围绕在 monad 变换器上,我可以得到一些玩具示例来工作,但在这里我正在努力处理一个更真实的用例。在 的基础上,使用 ExceptT
的更现实的示例,其中定义了三个辅助函数。
{-# LANGUAGE RecordWildCards #-}
-- imports so that the example is reproducible
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Trans.Except
import qualified Data.List as L
import Data.Text (Text)
import qualified Data.Text as T
import System.Random (Random (randomRIO))
-- a few type declarations so the example is easier to follow
newtype Error = Error Text deriving Show
newtype SQLQuery = SQLQuery Text deriving Show
newtype Name = Name { unName :: Text } deriving Show
data WithVersion = WithVersion { vName :: Name, vVersion :: Int }
-- | for each name, retrieve the corresponding version from an external data store
retrieveVersions :: [Name] -> ExceptT Error IO [WithVersion]
retrieveVersions names = do
doError <- liftIO $ randomRIO (True, False) -- simulate an error
if doError
then throwE $ Error "could not retrieve versions"
else do
let fv = zipWith WithVersion names [1..] -- just a simulation
pure fv
-- | construct a SQL query based on the names/versions provided
-- (note that this example is a toy with a fake query)
mkQuery :: [WithVersion] -> SQLQuery
mkQuery withVersions =
SQLQuery $ mconcat $ L.intersperse "\n" $ (\WithVersion {..} ->
unName vName <> ":" <> T.pack (show vVersion)
) <$> withVersion
-- | query an external SQL database and return result as Text
queryDB :: SQLQuery -> ExceptT Error IO Text
queryDB q = do
doError <- liftIO $ randomRIO (True, False) -- simulate an error
if doError
then throwE $ Error "SQL error"
else do
pure "This is the result of the (successful) query"
调用randomRIO
是为了模拟出错的可能性。如果 doError
是 True
,那么如果使用 Either
.
,那么助手 return 相当于 Left $ Error "message"
上面的所有帮助程序都可以正常编译,但是下面的示例包装函数无法编译:
-- | given a list of names, retrieve versions, build query and retrieve result
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
eitherResult <- runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
case eitherResult of
Left err -> throwE err
Right result -> pure result
GHC报错如下:
• Couldn't match type ‘IO’ with ‘ExceptT Error IO’
Expected type: ExceptT Error IO (Either Error Text)
Actual type: IO (Either Error Text)
• In a stmt of a 'do' block:
eitherResult <- runExceptT
$ do withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
我尝试使用各种函数代替 runExceptT
,即 runExcept
、withExcept
、withExceptT
,但其中 none 有效。我可以在 Expected
和 Actual
类型之间得到的闭包是 runExceptT
.
为了 retrieveValues
编译并正确地 return “或者” Error
或 Text
形式的结果应该改变什么?
我还认为在这里使用 caseEitherResult of
可能是多余的,因为它所做的只是传递结果或错误,没有额外的处理,所以我尝试了一个更直接的版本,但也失败了:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
仔细考虑您的各种 do
块使用的是什么单子。让我们先看看您对 retrieveValues
:
的第一个定义
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
eitherResult <- runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
case eitherResult of
Left err -> throwE err
Right result -> pure result
此函数位于 ExceptT Error IO
monad 中,这意味着顶部 do
块中的每个语句都需要位于该 monad 中。但是,您的第一个语句 eitherResult <- runExceptT $ do ...
并不存在。 runExceptT
的类型是 ExceptT e m a -> m (Either e a)
,在本例中特化为 ExceptT Error IO Text -> IO (Either Error Text)
,这意味着它存在于 IO
monad 中, 而不是 ExceptT Error IO
!要解决此问题,您需要 lift
结果。因此,该行应如下所示:
eitherResult <- lift $ runExceptT $ do
你的第二个定义也很接近工作,但是你在修改第一个定义时没有删除足够多的内容。您写道:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
您应该问自己的问题是:我什至需要第三行吗?换句话说,如果你的结果应该是 ExceptT Error IO Text
而你的内部 do
块是类型 ExceptT Error IO Text
,那么你为什么要调用 runExceptT
?或者,也许您的目标是生成 Either
作为此函数的结果,因此 runExceptT
很关键,但现在该类型没有意义。换句话说,有两种方法可以解决这个问题。首先,您可以修复实现以匹配类型,只需删除第三行:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
或者,您可以更改类型以匹配实现:
retrieveValues :: [Name] -> IO (Either Error Text)
retrieveValues names = do
runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
(请注意,通常只有一个语句的 do
块根本不需要在 do
块中。因此在这种情况下,您可以删除第一个 do
根本不改变程序。)
试图将我的头脑围绕在 monad 变换器上,我可以得到一些玩具示例来工作,但在这里我正在努力处理一个更真实的用例。在 ExceptT
的更现实的示例,其中定义了三个辅助函数。
{-# LANGUAGE RecordWildCards #-}
-- imports so that the example is reproducible
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Trans.Except
import qualified Data.List as L
import Data.Text (Text)
import qualified Data.Text as T
import System.Random (Random (randomRIO))
-- a few type declarations so the example is easier to follow
newtype Error = Error Text deriving Show
newtype SQLQuery = SQLQuery Text deriving Show
newtype Name = Name { unName :: Text } deriving Show
data WithVersion = WithVersion { vName :: Name, vVersion :: Int }
-- | for each name, retrieve the corresponding version from an external data store
retrieveVersions :: [Name] -> ExceptT Error IO [WithVersion]
retrieveVersions names = do
doError <- liftIO $ randomRIO (True, False) -- simulate an error
if doError
then throwE $ Error "could not retrieve versions"
else do
let fv = zipWith WithVersion names [1..] -- just a simulation
pure fv
-- | construct a SQL query based on the names/versions provided
-- (note that this example is a toy with a fake query)
mkQuery :: [WithVersion] -> SQLQuery
mkQuery withVersions =
SQLQuery $ mconcat $ L.intersperse "\n" $ (\WithVersion {..} ->
unName vName <> ":" <> T.pack (show vVersion)
) <$> withVersion
-- | query an external SQL database and return result as Text
queryDB :: SQLQuery -> ExceptT Error IO Text
queryDB q = do
doError <- liftIO $ randomRIO (True, False) -- simulate an error
if doError
then throwE $ Error "SQL error"
else do
pure "This is the result of the (successful) query"
调用randomRIO
是为了模拟出错的可能性。如果 doError
是 True
,那么如果使用 Either
.
Left $ Error "message"
上面的所有帮助程序都可以正常编译,但是下面的示例包装函数无法编译:
-- | given a list of names, retrieve versions, build query and retrieve result
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
eitherResult <- runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
case eitherResult of
Left err -> throwE err
Right result -> pure result
GHC报错如下:
• Couldn't match type ‘IO’ with ‘ExceptT Error IO’
Expected type: ExceptT Error IO (Either Error Text)
Actual type: IO (Either Error Text)
• In a stmt of a 'do' block:
eitherResult <- runExceptT
$ do withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
我尝试使用各种函数代替 runExceptT
,即 runExcept
、withExcept
、withExceptT
,但其中 none 有效。我可以在 Expected
和 Actual
类型之间得到的闭包是 runExceptT
.
为了 retrieveValues
编译并正确地 return “或者” Error
或 Text
形式的结果应该改变什么?
我还认为在这里使用 caseEitherResult of
可能是多余的,因为它所做的只是传递结果或错误,没有额外的处理,所以我尝试了一个更直接的版本,但也失败了:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
仔细考虑您的各种 do
块使用的是什么单子。让我们先看看您对 retrieveValues
:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
eitherResult <- runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
case eitherResult of
Left err -> throwE err
Right result -> pure result
此函数位于 ExceptT Error IO
monad 中,这意味着顶部 do
块中的每个语句都需要位于该 monad 中。但是,您的第一个语句 eitherResult <- runExceptT $ do ...
并不存在。 runExceptT
的类型是 ExceptT e m a -> m (Either e a)
,在本例中特化为 ExceptT Error IO Text -> IO (Either Error Text)
,这意味着它存在于 IO
monad 中, 而不是 ExceptT Error IO
!要解决此问题,您需要 lift
结果。因此,该行应如下所示:
eitherResult <- lift $ runExceptT $ do
你的第二个定义也很接近工作,但是你在修改第一个定义时没有删除足够多的内容。您写道:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
您应该问自己的问题是:我什至需要第三行吗?换句话说,如果你的结果应该是 ExceptT Error IO Text
而你的内部 do
块是类型 ExceptT Error IO Text
,那么你为什么要调用 runExceptT
?或者,也许您的目标是生成 Either
作为此函数的结果,因此 runExceptT
很关键,但现在该类型没有意义。换句话说,有两种方法可以解决这个问题。首先,您可以修复实现以匹配类型,只需删除第三行:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
或者,您可以更改类型以匹配实现:
retrieveValues :: [Name] -> IO (Either Error Text)
retrieveValues names = do
runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
(请注意,通常只有一个语句的 do
块根本不需要在 do
块中。因此在这种情况下,您可以删除第一个 do
根本不改变程序。)