Подтвердить что ты не робот

Написание Monad Transformer, действительно ли нужно так много жестко закодированных экземпляров

Я долгое время работал в монаде-трансформере, впервые создавал трансформатор монады.... И я чувствую, что сделал что-то ненужное.

Мы работаем над проектом с несколькими таблицами БД, а жесткое кодирование набора в разные стеки монадов становится громоздким, поэтому мы решили разбить его на разные подключаемые монадные трансформаторы, что позволяет нам выбирать на уровне типа функции, вроде этого

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 декларация!)

Есть ли более чистый способ сделать это?

4b9b3361

Ответ 1

Да, одна из проблем с монадными трансформаторами: когда вы добавляете новый трансформатор, вам приходится писать все большее число экземпляров шаблонов. Это n экземпляров каждый раз, для всего экземпляра O (n ^ 2). Например, вы можете наблюдать эту проблему масштабирования в mtl исходном коде. Трансформаторы Monad не являются легко расширяемыми.

Теперь, хороший процент монад, которые мы используем ежедневно, может быть выражен как некоторая комбинация трансформаторов, предоставляемых mtl, а это означает, что кто-то еще сделал работу по написанию всех этих скучных экземпляров. Но эти трансформаторы, конечно же, не охватывают каждую монаду, и вас укусят, когда вам нужно будет написать свой собственный.

Вот почему предпринимаются постоянные усилия по разработке новых подходов к печати. Хорошим примером в Haskell является Kiselyov et al. extensible-effects library, которая использует алгебраический подход для ввода текста на основе свободных монад. Дизайн этой библиотеки описан в двух статьях: Альтернатива Monad Transformers, в которой некоторое время описываются проблемы с подходом mtl и Более расширяемые эффекты, описывающие обновленную и оптимизированную реализацию библиотеки.

Если вы хотите посмотреть, как можно безопасно и расширяемо вводить эффект, см. Edwin Brady effects library для языка Idris. Существует довольно много ресурсов, объясняющих effects: tutorial, оригинальный Programming and Reasoning with Algebraic Effects и Ресурсно-зависимые алгебраические эффекты, описывающие некоторые новые функции effects. Вероятно, есть еще несколько ресурсов, которые я забыл в этом списке.