了解嵌套的Monad约束

时间:2020-06-01 18:27:38

标签: haskell io monads transformation monad-transformers

说我有以下类型:

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 AddressmakeAddress db的类型为MonadIO m => MonadError Error m -> Row -> m Address。假设xs的类型为[Row],则map (makeAddress db) xs应该给出[Addresses]

并且假设内部m和外部makeAddress(在findMany中)都是MonadIO类型类的实例,那么这些应该兼容吗? / p>

显然这是不正确的,但是我不知道我的推理在哪里出现问题,或者如何解决我的实现问题。

2 个答案:

答案 0 :(得分:4)

concat (map f list)要求f返回列表。这样map f list可以产生concat的列表。

因此,在您的代码中,您正在使用makeAddress选择m = [],以便map (makeAddress ...) xs :: [[Address]]concat (....) :: [Address]。现在,makeAddress要求单子m在类MonadIO中,而m = []不是,因此是错误的。

尝试使用类似的东西

...
then mapM (makeAddress db) xs
else ...

答案 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的monad,因此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]

¹如果您以前从未见过~,则可以在此答案中的所有位置将其替换为=,而不会失去任何重要意义。

相关问题