How could this capability model/pattern handle callbacks
Closed this issue ยท 6 comments
Maybe this is a beginner question and the solution is obvious, but how can this pattern work with callbacks? I'm trying to get something similar to the following to work for hours ๐ข REALLY!
class Monad m => Database m where
registerCallback :: (String -> m ()) -> m ()
data DatabaseImpl m = DatabaseImpl
{ doRegisterCallback :: (String -> m ()) -> m ()
}
instance
( Has (DatabaseImpl m) r
, Monad m
) => Database (ReaderT r m) where
registerCallback cb = asks getter >>= \(DatabaseImpl doRegisterCallback) -> lift . doRegisterCallback $ cb
While I understand that this leads to the cannot construct the infinite type: m ~ ReaderT r m problem, I have no idea how to avoid this. I would not like to remove the recursion completely since I would like to use other aspects - e.g. logging - both in data access (concrete database implementation) and in the callback (to allow the client code to add logging as well).
I've tried an approach like https://stackoverflow.com/questions/50139963/monadreader-foo-m-m-results-in-infinite-type-from-functional-dependency but failed to implement this properly.
So my guess is still that some of my assumptions are wrong - as a Haskell beginner with an OO background - and the solution is pretty simple; maybe just a few type adaptations are necessary; but I really need some support here. And since the code here is part of a tutorial I hope I'm allowed to ask for help here - even if this is not a classical issue kind of thing. Maybe this "real world problem" could improve further parts of the tutorial or at least this issue could be used as a reference for others encountering similar problems. Thanks in advance for any hint - and also thanks for this great tutorial bringing me to start to refactor my application towards a clear capability model.
Your problem becomes more obvious when spelling out the instance type:
instance
( Has (DatabaseImpl m) r
, Monad m
) => Database (ReaderT r m) where
--registerCallback :: (String -> ReaderT r m ()) -> ReaderT r m ()
registerCallback cb = do
r <- ask
let
run :: (String -> ReaderT r m ()) -> (String -> m ())
run f s = runReaderT (f s) r
reg :: (String -> m ()) -> m ()
reg = doRegisterCallback . getter $ r
lift . reg $ run cb
It meant that the type that didn't line up was cb
- it was (String -> ReaderT r m ())
whereas you required (String -> m ())
.
I hope that makes sense. There's probably less convoluted ways to do this, but an initial hackety hack this morning revealed this solution.
Thanks a lot! For sure your solution works as expected - but to be honest I didn't understand the code to a level so that I'm able to adapt it to a only a little more complex situation. So may I ask you one additional question - so that I am maybe able to recognize the pattern behind your solution? How would this look like for a additional nested callback?
class Monad m => Database m where
registerCallback :: ((String -> m ()) -> m ()) -> m ()
Do the same exercise as I showed you above ๐
If you have turn on {-# LANGUAGE InstanceSigs #-}
you can add the signature yourself to see what the additional m
s turn into ๐
Thanks - it's relatively simple... once figured out ๐
instance (Has (BrokerImplComplex m) r, Monad m) => BrokerComplex (ReaderT r m) where
actComplex :: ((String -> ReaderT r m Char) -> ReaderT r m Int) -> ReaderT r m Bool
actComplex cb = do
r <- ask
let
run :: ((String -> ReaderT r m Char) -> ReaderT r m Int) -> (String -> m Char) -> m Int
run f s = runReaderT (f (lift . s)) r
reg :: ((String -> m Char) -> m Int) -> m Bool
reg = doActComplex . getter $ r
lift (reg $ run cb)
Once again - THANK YOU - for your assistance - your blog post was a great inspiration for me as a haskell beginner.
Happy to see you solved it! ๐
I'll try to get started on the next post during the weekend ๐