Polysemy: Design heuristics: Effects layering

In our previous log, we have seen that we had to problematics:

  • Splitting effects by concerns
  • Having one target implementation

A way to mitigate that is to have well-seperated, consumer-facing effects:

data UserRegistration (m :: Type -> Type) (a :: Type) where
  SignUp :: UserInfo -> Password -> UserRegistration m (Either SignUpError ())
  ConfirmSignUp :: ConfirmationInfo -> UserRegistration m Bool

makeSem ''UserRegistration

data UserAuthenticationManagement (m :: Type -> Type) (a :: Type) where
  SignIn :: EmailAddress -> Password -> UserAuthenticationManagement m (Either SignInError ())
  SignOut :: UserToken -> UserAuthenticationManagement m ()

makeSem ''UserAuthenticationManagement

data UserAuthenticationCheck (m :: Type -> Type) (a :: Type) where
  AuthenticateUser :: UserToken -> UserAuthenticationCheck m (Either AuthenticateUserError AuthenticatedUser)

makeSem ''UserAuthenticationCheck

And one internal effect:

data InternalUserEffect (m :: Type -> Type) (a :: Type) where
  InternalSignUp :: UserInfo -> Password -> InternalUserEffect m (Either SignUpError ())
  InternalConfirmSignUp :: ConfirmationInfo -> InternalUserEffect m Bool
  InternalSignIn :: EmailAddress -> Password -> InternalUserEffect m (Either SignInError ())
  InternalSignOut :: UserToken -> InternalUserEffect m ()
  InternalAuthenticateUser :: UserToken -> InternalUserEffect m (Either AuthenticateUserError AuthenticatedUser)

makeSem ''InternalUserEffect

Finally we have an interpreter per user-facing effect:

intrepretUserRegistration :: Member InternalUserEffect r => InterpreterFor UserRegistration r
intrepretUserRegistration =
  interpret $
    \case
      SignUp userInfo password -> internalSignUp userInfo password
      ConfirmSignUp confirmationInfo -> internalConfirmSignUp confirmationInfo

intrepretUserAuthenticationManagement :: Member InternalUserEffect r => InterpreterFor UserAuthenticationManagement r
intrepretUserAuthenticationManagement =
  interpret $
    \case
      SignIn emailAddress password -> internalSignIn emailAddress password
      SignOut token -> internalSignOut token

intrepretUserAuthenticationCheck :: Member InternalUserEffect r => InterpreterFor UserAuthenticationCheck r
intrepretUserAuthenticationCheck =
  interpret $
    \case
      AuthenticateUser token -> internalAuthenticateUser token

See the full the code here.