Haskell 有 3 个单子结果时的 mapM

Haskell's mapM when there are 3 monadic results

我一次又一次地将 运行 保留在 Haskell 代码中,我觉得我应该能够使用 mapM 来使对 monad 的操作更干净,但是如果我有两个m (t a) 类型的计算,也许还有另一个使用 t 的操作,类似于 a -> t a(通常是 Maybe)...假设我有这个持久代码:

findDealership :: MonadIO m => Key Vehicle -> SqlPersistT m (Maybe (Entity Dealership))
findDealership vehicleKey = mapM selectDealership (getVehicleDealership vehicleKey)

getVehicleDealership :: MonadIO m => Key Vehicle -> SqlPersistT m (Maybe (Key Dealership))
getVehicleDealership vehicleKey =
  (\x -> x >>= vehicleDealershipId . entityVal) <$> getEntity vehicleKey

selectDealership :: MonadIO m => Key Dealership -> SqlPersistT m (Maybe (Entity Dealership))
selectDealership dealershipKey = selectFirst [DealershipId ==. dealershipKey] []

其中 vehicleDealershipId 是类型 Maybe (Key Dealership) 的字段,上面的代码将无法编译,我无法计算出 >>= 和 [= 的正确组合14=] 的....我觉得这个问题有点像 mapM 正在解决的问题,只是那里有另一个级别的 monad...你可以得到 SqlPersistT m (Maybe (Maybe (Maybe (Key Dealership)))) 但是显然我需要将内部类型完全压平...

以上代码的错误输出:

    • Couldn't match type ‘Control.Monad.Trans.Reader.ReaderT
                             SqlBackend m0’
                     with ‘Maybe’
      Expected type: SqlPersistT m (Maybe (Entity Dealership))
        Actual type: Control.Monad.Trans.Reader.ReaderT
                       SqlBackend
                       m
                       (Control.Monad.Trans.Reader.ReaderT
                          SqlBackend m0 (Maybe (Entity Dealership)))
    • In the expression:
        mapM selectDealership $ getVehicleDealership vehicleKey
      In an equation for ‘findDealership’:
          findDealership vehicleKey
            = mapM selectDealership $ getVehicleDealership vehicleKey
   |
46 | findDealership vehicleKey = mapM selectDealership $ getVehicleDealership vehicleKey
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

和:

    • Couldn't match type ‘Maybe (Key Dealership)’
                     with ‘Key Dealership’
      Expected type: Control.Monad.Trans.Reader.ReaderT
                       SqlBackend m0 (Key Dealership)
        Actual type: SqlPersistT m0 (Maybe (Key Dealership))
    • In the second argument of ‘($)’, namely
        ‘getVehicleDealership vehicleKey’
      In the expression:
        mapM selectDealership $ getVehicleDealership vehicleKey
      In an equation for ‘findDealership’:
          findDealership vehicleKey
            = mapM selectDealership $ getVehicleDealership vehicleKey
   |
46 | findDealership vehicleKey = mapM selectDealership $ getVehicleDealership vehicleKey
   |                                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

当你想压平几个Maybe时,可以使用join。它适用于任何 Monad,但最常出现在 Maybe

getVehicleDealership :: MonadIO m => Key Vehicle -> SqlPersistT m (Maybe (Key Dealership))
getVehicleDealership vehicleKey =
    join . fmap (vehicleDealershipId . entityVal) <$> getEntity vehicleKey

mapM(又称 traverse)在很多情况下都很有用,但我认为它不适用于这里。 Maybe 出现的顺序无关紧要,您想要 SqlPersistT m (Maybe a),而不是 Maybe (SqlPersistT m a)

我现在意识到,我想要的是 MaybeT

我的示例可以使用 MaybeT 重写,如下所示:

findDealership :: MonadIO m => Key Vehicle -> SqlPersistT m (Maybe (Entity Dealership))
findDealership vehicleKey = runMaybeT $ do
  dealershipKey <- MaybeT (getVehicleDealership vehicleKey) 
  selectDealership dealershipKey

getVehicleDealership :: MonadIO m => Key Vehicle -> SqlPersistT m (Maybe (Key Dealership))
getVehicleDealership vehicleKey = runMaybeT $ do
  vehicleEntity <- MaybeT (getEntity vehicleKey)
  hoistMaybe $ vehicleDealershipId (entityVal vehicleEntity)

selectDealership :: MonadIO m => Key Dealership -> SqlPersistT m (Maybe (Entity Dealership))
selectDealership dealershipKey = 
  selectFirst [DealershipId ==. dealershipKey] []