A software engineer website

From custom Monad to Polysemy

Gautier DI FOLCO October 29, 2023 [dev] #haskell #design #polysemy

Previously we have studied a custom Monad and how to refactor it to make it easier to work with.

For reminder, it was this one:

class Monad m => MyAppMonad m where
  getSmtpConfig :: m SmtpConfig
  getS3Config :: m S3Config
  getRedisParams :: m RedisParams

And it was used as a basis for:

fetchUser :: (MyAppMonad m) => UserName -> m (Maybe User)
storeAvatar :: (MyAppMonad m) => UserId -> ByteString -> m ()
notifySignUp :: (MyAppMonad m) => UserId -> PassCode -> m ()

At some point, we would like to integrate it with a broader Polysemy-based codebase.

A first move to make would be to replace each reader by Polysemy Readers effects:

type MyAppMonadEffects =
  '[ Reader SmtpConfig,
     Reader S3Config,
     Reader RedisParams
   ]

Then, to avoid any change in the codebase, we have to drop in MyAppMonad with a fixed EffectsRow:

type MyAppMonad m = m ~ Sem MyAppMonadEffects

And then our getters:

getSmtpConfig :: (Member (Reader SmtpConfig) r) => Sem r SmtpConfig
getSmtpConfig = ask

getS3Config :: (Member (Reader S3Config) r) => Sem r S3Config
getS3Config = ask

getRedisParams :: (Member (Reader RedisParams) r) => Sem r RedisParams
getRedisParams = ask

It's not ideal as effects being fixed dealing with this code (calling/being called by) would require raise each time effects differ.

But that's a first step, then we can progressively substitute consumers with Sem r/Members and create individual effects/interpreters.