【发布时间】:2021-05-23 14:50:24
【问题描述】:
试图将我的头包裹在 monad 转换器上,我可以得到一些玩具示例来工作,但在这里我正在努力解决一个稍微更真实的用例。以this previous question 为基础,使用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,但它们都不起作用。我可以在Expected 和Actual 类型之间获得的封闭是runExceptT。
为了使retrieveValues 编译并正确返回“要么”Error 或Text 形式的结果,应该进行哪些更改?
我还认为在这里使用caseEitherResult of 可能是多余的,因为它所做的只是传递结果或错误,没有额外的处理,所以我尝试了一个更直接的版本,但也失败了:
retrieveValues :: [Name] -> ExceptT Error IO Text
retrieveValues names = do
runExceptT $ do
withVersions <- retrieveVersions names
let query = mkQuery withVersions
queryDB query
【问题讨论】:
-
你根本不需要
runExceptT。retrieveValues函数声明的返回类型是ExceptT,而不是IO (Either Error Text) -
现在我觉得很尴尬:)
-
@4castle 感谢这个(洞察力非常明显)的答案。这可能是这个示例事物与我正在尝试构建的真实世界事物的更大背景之间存在很大混淆的结果。现在这完美无缺。
标签: haskell monads monad-transformers