2016-09-25 3 views
3

Ist es möglich, die folgende Funktion als Einzeiler umzuschreiben?Wie komponieren Funktionen arbeiten mit ReaderT und Entweder?

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

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

+0

@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? –

+0

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

Antwort

3

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.

  1. 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) 
    
  2. 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 
    
+0

Sie schlagen also vor, "Entweder" ganz loszuwerden? –

+0

@SaurabhNanda Überhaupt nicht! Das "Entweder" ist gerade versteckt, nicht weg: 'AusgenommenT e m a 'ist isomorph zu' m (Entweder e a) '. Der Grund "ExceptT ema" ist schöner als 'm (Entweder ea)' ist, weil seine 'Monad' -Instanz' (>> =) 'Operation kümmert sich um die Zerstörung und Rekonstruktion der' Entweder für Sie, und kümmert sich um kurz -circuiting wenn ein "Left" erscheint. –

+0

Aber wenn ich 'ExceptT' Teil meiner based' AppM' Monade mache, wird das nicht ALLE meine Funktionen zwingen, ein 'Left' oder' Right' zurück zu geben? Auch wenn sie nicht wirklich zu einem Fehler führen können? –

Verwandte Themen