如何组合使用ReaderT和Either操作的功能?

时间:2016-09-25 17:46:12

标签: haskell monads functor monad-transformers

是否可以将以下功能重写为单行?

action :: NewTenant -> AppM (Either TenantCreationError Tenant)
action newTenant = (createTenant newTenant) >>= \case
  Left x -> return $ Left x
  Right y -> do
    t <- activateTenant $ (y ^. key)
    return $ Right t

type AppM = ReaderT AppConfig IO
createTenant :: NewTenant -> AppM (Either TenantCreationError Tenant)
activateTenant :: TenantId -> AppM Tenant

1 个答案:

答案 0 :(得分:3)

可能最好的方法是在ExceptT monad中加入AppM或类似内容。然后,您将为createTenantactivateTenant提供新类型:

createTenant :: NewTenant -> AppM Tenant
activateTenant :: TenantId -> AppM Tenant

action :: NewTenant -> AppM Tenant
action = activateTenant . view key <=< createTenant

您可以使用ExceptT(适用于createTenant)和lift(适用于activateTenant)将旧功能转换为新的monad堆栈。

如果由于某种原因这种方法无法实现,那么您可以使代码适当地不可读:

action = createTenant >=> either (return . Left) (\y -> Right <$> activateTenant (y ^. key))

ExceptT放入AppM monad的一个缺点是,你无法区分能够和不能失败的行为。如果这对您很重要,您有几个选择。

  1. 仅在本地使用ExcepT作为其实例。您会按原样保留AppM以及createTenantactivateTenant的类型,但请写

    action newTenant = runExceptT $ do
        y <- ExcepT (createTenant newTenant)
        lift (activateTenant (y ^. key))
    

    或其单行等价物:

    action n = runExcepT (ExceptT (createTenant n) >>= lift . activateTenant . view key)
    
  2. 让您的行为对其效果具有多态性。您仍会在ExceptT monad中加入AppM,但createTenantactivateTenant的类型现在将

    createTenant :: (MonadReader AppConfig m, MonadIO m, MonadThrow TenantCreationError m)
                 => NewTenant -> m Tenant
    activateTenant :: (MonadReader AppConfig m, MonadIO m)
                   => TenantId -> m Tenant
    
    action :: (MonadReader AppConfig m, MonadIO m, MonadThrow TenantCreationError m)
           => NewTenant => m Tenant
    action = activateTenant . view key <=< createTenant
    

    然后,您将特别能够给action单态类型AppM Tenant;从activateTenant的类型中可以清楚地看出它不会失败。而且它会让你有机会说出你之前无法说出的话;例如如果newTenant不需要执行IO,您可以通过从其类型中的约束中删除MonadIO m来表明。您可以通过为预期最常使用的组合定义类型同义词来恢复短类型签名,例如

    type ConfigIO m = (MonadReader AppConfig m, MonadIO m)
    type Failable m = (ConfigIO m, MonadThrow TenantCreationError m)
    createTenant :: Failable m => NewTenant -> m Tenant
    activateTenant :: ConfigIO m => TenantId -> m Tenant