Polysemy: RW effects
I haven't written on polysemy in a while, let's change that.
Previously, when I had to create effects on a same topic, but with different access patterns, I would create distinct effects/interpreters:
data SomethingWrite (m :: Type -> Type) a where
Write :: SomethingWrite m ()
makeSem ''SomethingWrite
data SomethingRead (m :: Type -> Type) a where
Read :: SomethingRead m ()
makeSem ''SomethingRead
runSomethingWrite =
interpret $
\case
Write -> return ()
runSomethingRead =
interpret $
\case
Read -> return ()
consumerWrite = write
consumerRead = read
consumerBoth = consumerWrite >> consumerRead
runConsumers =
run $
runSomethingWrite $
runSomethingRead consumerBoth
Alternatively, we could define one, but add a phantom type, which has a constraint:
data AccessLevel = R | W | RW
type family CanRead accessLevel :: Constraint where
CanRead 'R = ()
CanRead 'RW = ()
type family CanWrite accessLevel :: Constraint where
CanWrite 'W = ()
CanWrite 'RW = ()
data Something (accessLevel :: AccessLevel) (m :: Type -> Type) a where
Write' :: forall accessLevel m. CanWrite accessLevel => Something accessLevel m ()
Read' :: forall accessLevel m. CanRead accessLevel => Something accessLevel m ()
makeSem ''Something
runSomethingRW =
interpret $
\case
Read' -> return ()
Write' -> return ()
We could safely define a "partial" interpreter:
runSomethingR =
interpret $
\case
Read' -> return ()
Note: GHC will emit an incompleteness alter because Haskell constraints do not carry closeness.
We can then use this new implementation, composing constraints:
consumerWrite' = write'
consumerRead' = read'
consumerBoth' = consumerWrite' >> consumerRead'
runConsumers' =
run $
runSomethingRW consumerBoth'