理解嵌套的Monad约束

问题描述 投票:0回答:1

假设我有以下类型。

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 dbMonadIO m => MonadError Error m -> Row -> m Address. 鉴于 xs 有型 [Row], map (makeAddress db) xs 应给 [Addresses].

而且,鉴于内部和外部 m (在 makeAddress 并在 findMany)的一个实例。MonadIO 类型类,这些应该是兼容的单体?

显然这是不正确的,但我不知道我的推理在哪里出了问题,也不知道因此如何修正我的实现。

haskell io monads transformation monad-transformers
1个回答
3
投票

你说:"当然。

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的单体,所以 MonadIO m 约束不满足。呜呜!

而不是 concat,您可以使用 sequenceA:

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]

如果你没看过 ~ 之前,你可以用 = 在这个答案中,到处都是,没有任何重要的东西会丢失。


4
投票

concat (map f list) 要求 f 来返回一个列表。这是如此 map f list 可以产生一个列表的列表,以 concat.

因此,在你的代码中,你使用的是 makeAddress 选择 m = [] 以致于 map (makeAddress ...) xs :: [[Address]]concat (....) :: [Address]. 现在.., makeAddress 要求单体 m 是类 MonadIOm = [] 不是,因此出现错误。

尝试使用类似

...
then mapM (makeAddress db) xs
else ...
© www.soinside.com 2019 - 2024. All rights reserved.