Я долгое время работал в монаде-трансформере, впервые создавал трансформатор монады.... И я чувствую, что сделал что-то ненужное.
Мы работаем над проектом с несколькими таблицами БД, а жесткое кодирование набора в разные стеки монадов становится громоздким, поэтому мы решили разбить его на разные подключаемые монадные трансформаторы, что позволяет нам выбирать на уровне типа функции, вроде этого
doSomething::(HasUserTable m, HasProductTable m)=>Int->m String
(HasXTable - класс, XTableT - это конкретный трансформатор монады). Эти отдельные монадные трансформаторы могут быть вставлены или удалены полностью модульным способом и будут хранить дескрипторы DB, требуют ResourceT и т.д....
Моя первая попытка состояла в том, чтобы просто обернуть вокруг ReaderT, который будет использоваться для хранения дескриптора DB. Сразу стало очевидным, что это не сработает, так как ReaderT (и StateT и т.д.) Не могут быть уложены без использования цепей жестко закодированных "подъемов", тем самым нарушая подключаемую модульность элементов стека.
Единственное решение, казалось, заключалось в написании полностью отдельных копий монады ReaderT, каждый из которых позволял доступ к остальным на более низком уровне. Это работает, но решение заполнено кодом шаблона, что-то вроде этого
class HasUserTable m where
getUser::String->m User
newtype UserTableT m r = UserTableT{runUserTableT::String->m r}
--Standard monad instance stuff, biolerplate copy of ReaderT
instance Functor m=>Functor (UserTableT m) where....
instance Applicative m=>Applicative (UserTableT m) where....
instance Monad m=>Monad (UserTableT m) where....
instance Monad m=>HasUserTable (UserTableT m) where....
--Gotta hardcode passthrough rules to every other monad transformer
--in the world, mostly using "lift"....
instance MonadTrans BlockCacheT where....
instance (HasUserTable m, Monad m)=>HasUserTable (StateT a m)....
instance (HasUserTable m, Monad m)=>HasUserTable (ResourceT m)....
.... etc for all other monad transformers
--Similarly, need to hardcode passthrough rules for all other monads
--through the newly created one
instance MonadResource m=>MonadResource (UserTableT m) where....
instance MonadState a m=>MonadState a (UserTableT m) where....
instance (MonadBaseControl IO m) => MonadBaseControl IO (UserTableT m)....
.... etc for all other monad transformers
Что еще хуже, так это то, что нам нужно добавить еще более простые правила для каждого нового добавляемого нами монадного преобразователя (т.е. каждая новая таблица, которую мы добавляем, должна перешагнуть через все остальные монадные трансформаторы таблицы, поэтому нам нужен пример n ^ 2 декларация!)
Есть ли более чистый способ сделать это?