了解嵌套的 Monad 约束
Understanding nested Monad constraints
假设我有以下类型:
data Row = Row
{
id :: !AddressID
}
具有以下内部转换功能:
makeAddress :: MonadIO m => MonadError Error m => Connection -> Row -> m Address
makeAddress _ Row{..} = return $ Address "Potato"
然后我有以下函数使用Postgres.Simple从数据库中读取:
findMany
:: MonadIO m
=> MonadReader Context m
=> MonadError Error m
=> [AddressID]
-> m [Address]
findMany ids = do
db <- view Context.db
xs <- liftIO $ PG.query db sql_query_addr $ PG.Only (PG.In (map unAddressId ids))
if (length xs) == (length ids)
then do
let addresses = concat (map (makeAddress db) xs)
return addresses
else
throwError $ AddressNotFound Nothing
-----------------------------------------------------------------------------------------------------------------------
sql_query_addr :: PG.Query
sql_query_addr = [qms|
SELECT *
FROM addresses a
WHERE a.id in ?
|]
编译失败:
• Could not deduce (MonadIO [])
arising from a use of ‘makeAddress’
from the context: (MonadIO m, MonadReader Context m,
MonadError Error m)
bound by the type signature for:
findMany :: forall (m :: * -> *).
(MonadIO m, MonadReader Context m, MonadError Error m) =>
[AddressID] -> m [Address]
at app/Impl/ReadModelApi/FindMany.hs:(22,1)-(27,18)
• In the first argument of ‘map’, namely ‘(makeAddress db)’
In the first argument of ‘concat’, namely
‘(map (makeAddress db) xs)’
In the expression: concat (map (makeAddress db) xs)
|
34 | let quotations = concat (map (makeAddress db) xs)
| ^^^^^^^^^^^^^^^^^
我意识到我的 makeAddress
函数不必要地复杂,这是一个最小的案例,从一个更大、更多副作用的转换函数中归结出来。
但是我不明白为什么编译失败,我以为:
鉴于此类型:makeAddress :: MonadIO m => MonadError Error m => Connection -> Row -> m Address
,makeAddress db
的类型是 MonadIO m => MonadError Error m -> Row -> m Address
。给定 xs
的类型为 [Row]
,map (makeAddress db) xs
应该给出 [Addresses]
.
并且鉴于内部和外部 m
(在 makeAddress
和 findMany
中)都是 MonadIO
类型类的实例,这些应该是兼容的单子?
显然这是不正确的,但我不知道我的推理在哪里失败,也不知道如何修复我的实现。
concat (map f list)
需要 f
到 return 一个列表。这样 map f list
就可以生成列表列表 concat
。
因此,在您的代码中,您使用 makeAddress
选择 m = []
以便 map (makeAddress ...) xs :: [[Address]]
和 concat (....) :: [Address]
。现在,makeAddress
要求 monad m
在 class MonadIO
中,但 m = []
不是,因此出现错误。
尝试使用类似
的东西
...
then mapM (makeAddress db) xs
else ...
你说:
makeAddress :: MonadIO m => MonadError Error m => Connection -> Row -> m Address
当然可以。并且:
makeAddress db :: MonadIO m => MonadError Error m -> Row -> m
足够接近了。最后实际上是 m Address
,但我认为这只是一个错字。并且:
map (makeAddress db) xs :: [Address]
这是你的第一个错误。你输了m
!它实际上是:
map (makeAddress db) xs :: MonadIO m => MonadError Error m => [m Address]
错误的解释是我们有
concat :: [[a]] -> [a]
因此,要使 [m Address]
等于 [[a]]
,我们必须选择 m ~ []
和 a ~ Address
¹;但是 []
不是可以执行 IO 的 monad,因此不满足 MonadIO m
约束。哎呀!
您可以使用 sequenceA
:
而不是 concat
sequenceA :: Applicative m => [m a] -> m [a]
-- OR, specializing,
sequenceA :: MonadIO m => MonadError m => [m Address] -> m [Address]
这个map
-sequenceA
组合很常见,它有自己的名字:
traverse :: Applicative m => (a -> m b) -> [a] -> m [b]
¹ 如果您以前没有见过 ~
,您可以将此答案中的任何地方替换为 =
,并且不会丢失任何重要内容。
假设我有以下类型:
data Row = Row
{
id :: !AddressID
}
具有以下内部转换功能:
makeAddress :: MonadIO m => MonadError Error m => Connection -> Row -> m Address
makeAddress _ Row{..} = return $ Address "Potato"
然后我有以下函数使用Postgres.Simple从数据库中读取:
findMany
:: MonadIO m
=> MonadReader Context m
=> MonadError Error m
=> [AddressID]
-> m [Address]
findMany ids = do
db <- view Context.db
xs <- liftIO $ PG.query db sql_query_addr $ PG.Only (PG.In (map unAddressId ids))
if (length xs) == (length ids)
then do
let addresses = concat (map (makeAddress db) xs)
return addresses
else
throwError $ AddressNotFound Nothing
-----------------------------------------------------------------------------------------------------------------------
sql_query_addr :: PG.Query
sql_query_addr = [qms|
SELECT *
FROM addresses a
WHERE a.id in ?
|]
编译失败:
• Could not deduce (MonadIO [])
arising from a use of ‘makeAddress’
from the context: (MonadIO m, MonadReader Context m,
MonadError Error m)
bound by the type signature for:
findMany :: forall (m :: * -> *).
(MonadIO m, MonadReader Context m, MonadError Error m) =>
[AddressID] -> m [Address]
at app/Impl/ReadModelApi/FindMany.hs:(22,1)-(27,18)
• In the first argument of ‘map’, namely ‘(makeAddress db)’
In the first argument of ‘concat’, namely
‘(map (makeAddress db) xs)’
In the expression: concat (map (makeAddress db) xs)
|
34 | let quotations = concat (map (makeAddress db) xs)
| ^^^^^^^^^^^^^^^^^
我意识到我的 makeAddress
函数不必要地复杂,这是一个最小的案例,从一个更大、更多副作用的转换函数中归结出来。
但是我不明白为什么编译失败,我以为:
鉴于此类型:makeAddress :: MonadIO m => MonadError Error m => Connection -> Row -> m Address
,makeAddress db
的类型是 MonadIO m => MonadError Error m -> Row -> m Address
。给定 xs
的类型为 [Row]
,map (makeAddress db) xs
应该给出 [Addresses]
.
并且鉴于内部和外部 m
(在 makeAddress
和 findMany
中)都是 MonadIO
类型类的实例,这些应该是兼容的单子?
显然这是不正确的,但我不知道我的推理在哪里失败,也不知道如何修复我的实现。
concat (map f list)
需要 f
到 return 一个列表。这样 map f list
就可以生成列表列表 concat
。
因此,在您的代码中,您使用 makeAddress
选择 m = []
以便 map (makeAddress ...) xs :: [[Address]]
和 concat (....) :: [Address]
。现在,makeAddress
要求 monad m
在 class MonadIO
中,但 m = []
不是,因此出现错误。
尝试使用类似
的东西...
then mapM (makeAddress db) xs
else ...
你说:
makeAddress :: MonadIO m => MonadError Error m => Connection -> Row -> m Address
当然可以。并且:
makeAddress db :: MonadIO m => MonadError Error m -> Row -> m
足够接近了。最后实际上是 m Address
,但我认为这只是一个错字。并且:
map (makeAddress db) xs :: [Address]
这是你的第一个错误。你输了m
!它实际上是:
map (makeAddress db) xs :: MonadIO m => MonadError Error m => [m Address]
错误的解释是我们有
concat :: [[a]] -> [a]
因此,要使 [m Address]
等于 [[a]]
,我们必须选择 m ~ []
和 a ~ Address
¹;但是 []
不是可以执行 IO 的 monad,因此不满足 MonadIO m
约束。哎呀!
您可以使用 sequenceA
:
concat
sequenceA :: Applicative m => [m a] -> m [a]
-- OR, specializing,
sequenceA :: MonadIO m => MonadError m => [m Address] -> m [Address]
这个map
-sequenceA
组合很常见,它有自己的名字:
traverse :: Applicative m => (a -> m b) -> [a] -> m [b]
¹ 如果您以前没有见过 ~
,您可以将此答案中的任何地方替换为 =
,并且不会丢失任何重要内容。