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 =
interpret $
\case
SignUp userInfo password -> internalSignUp userInfo password
ConfirmSignUp confirmationInfo -> internalConfirmSignUp confirmationInfo
intrepretUserAuthenticationManagement =
interpret $
\case
SignIn emailAddress password -> internalSignIn emailAddress password
SignOut token -> internalSignOut token
intrepretUserAuthenticationCheck =
interpret $
\case
AuthenticateUser token -> internalAuthenticateUser token
See the full the code here.