是否可以将以下功能重写为单行?
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
答案 0 :(得分:3)
可能最好的方法是在ExceptT
monad中加入AppM
或类似内容。然后,您将为createTenant
和activateTenant
提供新类型:
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的一个缺点是,你无法区分能够和不能失败的行为。如果这对您很重要,您有几个选择。
仅在本地使用ExcepT
作为其实例。您会按原样保留AppM
以及createTenant
和activateTenant
的类型,但请写
action newTenant = runExceptT $ do
y <- ExcepT (createTenant newTenant)
lift (activateTenant (y ^. key))
或其单行等价物:
action n = runExcepT (ExceptT (createTenant n) >>= lift . activateTenant . view key)
让您的行为对其效果具有多态性。您仍会在ExceptT
monad中加入AppM
,但createTenant
和activateTenant
的类型现在将
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