Wahrscheinlich die beste Art und Weise wird in Ihrem AppM
Monade ExceptT
oder ähnliches enthalten. Dann werden Sie neue Arten zu createTenant
geben und activateTenant
:
createTenant :: NewTenant -> AppM Tenant
activateTenant :: TenantId -> AppM Tenant
action :: NewTenant -> AppM Tenant
action = activateTenant . view key <=< createTenant
Sie können Ihre alten Funktionen auf den neuen Monade Stapel mit ExceptT
(für createTenant
) und lift
(für activateTenant
) konvertieren.
Wenn aus irgendeinem Grund dieser Ansatz nicht möglich ist, dann können Sie Ihren Code in geeigneter Weise unlesbar stattdessen machen:
action = createTenant >=> either (return . Left) (\y -> Right <$> activateTenant (y ^. key))
Ein Nachteil von ExceptT
in Ihrem AppM
Monade setzen ist, dass dann Sie keine Möglichkeit haben, zu unterscheiden zwischen Aktionen, die nicht scheitern können und müssen. Wenn Ihnen das wichtig ist, haben Sie eine Auswahl.
Verwenden Sie lokal ExcepT
nur für seine Instanzen. Sie würden AppM
halten, wie sie ist, und die Arten von createTenant
und activateTenant
wie sie ist, aber
action newTenant = runExceptT $ do
y <- ExcepT (createTenant newTenant)
lift (activateTenant (y ^. key))
oder dessen einzeilige Äquivalent schreiben:
action n = runExcepT (ExceptT (createTenant n) >>= lift . activateTenant . view key)
Machen Sie Ihre Aktionen polymorphen über deren Auswirkungen. Sie würden immer noch ExceptT
im AppM
Monade enthalten, aber die Typen von createTenant
und activateTenant
wäre jetzt
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
Sie würden dann insbesondere in der Lage sein action
den monomorphic Typ geben AppM Tenant
; und es wäre immer noch klar von der Art von activateTenant
, dass es nicht fehlschlagen kann. Außerdem würde es Ihnen die Möglichkeit geben, Dinge zu sagen, die Sie vorher nicht sagen konnten; z.B. Wenn newTenant
nicht IO
tun müssen, können Sie dies angeben, indem Sie MonadIO m
aus den Einschränkungen in seinem Typ entfernen. Sie können kurze Typ-Signaturen wiederherstellen, indem Sie ein Typ-Synonym für die Kombination (en) definieren, die am häufigsten verwendet werden, z.
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
Auf Kosten der Verwendung von [ 'Data.Bifunctor'] (https://hackage.haskell.org/package/bifunctors-3.2.0.1/docs/Data-Bifunctor.html) (aus 'bifunctors'), können Sie mit' bimap' auf beide Seiten des 'Entweder' gleichzeitig abbilden. Das bringt Ihnen 'createTenant newTenant >> = bimap pure (\ y -> activateTenant (y ^. Key))' (und natürlich können Sie auch den letzten Lambda-Punkt frei machen, um die Dinge noch weniger lesbar zu machen : 'createTenant newTenant >> = bimap pure (activateTenant. (^. key))'). – Alec
@Alec Naiv, das sieht nicht so aus, als ob es Typ-Checks gibt. Wird 'bimap' nicht ein' Entweder' mit einigen 'AppM's zurückgeben, wenn du ein' AppM' mit einem 'Entweder' drin hast? –
Ah ja. So würdest du. Da gibt es ['uncozipL'] (http://hackage.haskell.org/package/adjunctions-4.3/docs/Data-Functor-Adjunction.html#v:uncozipL) dafür in einem von Edward Kmetts Paket ...:) – Alec