1

Now and again I keep running into Haskell code where I feel like I should be able to use mapM to make operating over the monads cleaner, but if I have two computations of type m (t a), and perhaps there is another operation that uses t, something like a -> t a (typically this is Maybe)... say I have this persistent code:

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] []

Where vehicleDealershipId is a field of type Maybe (Key Dealership), the above code will not compile and I'm unable to work out the right combination of >>='s and mapM's.... I feel this problem is a bit like the one mapM is solving, only there is another level of monad in there... you can end up with SqlPersistT m (Maybe (Maybe (Maybe (Key Dealership)))) but obviously I need the inner type to be completely flattened...

Error output for the above code:

    • 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
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

and:

    • 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
   |                                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
danbroooks
  • 2,712
  • 5
  • 21
  • 43
  • Can you post the output of the error? – Robert Jul 19 '18 at 13:51
  • What's `t` when it isn't a `Maybe`? Is it intended to be any `Traversable`? If so, then this seems related to https://stackoverflow.com/questions/33189135/compose-nested-monads-in-haskell – K. A. Buhr Jul 19 '18 at 14:47

2 Answers2

3

When you want to flatten several Maybes, you can use join. It works for any Monad, but comes up most often for Maybe.

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

mapM (AKA traverse) is useful in many situations, but I don't think it applies here. It doesn't matter which order the Maybes appear, and you want SqlPersistT m (Maybe a), not Maybe (SqlPersistT m a).

bergey
  • 3,041
  • 11
  • 17
0

What I have come to realise now, is that what I wanted here was MaybeT.

My example can be re-written using MaybeT like so:

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] []
danbroooks
  • 2,712
  • 5
  • 21
  • 43