Отличный вопрос!
Преобразователь монад - это тип, который добавляет некоторые функциональные возможности к произвольной базовой монаде, сохраняя при этом монадность.К сожалению, монадные преобразователи невыразимы в C #, потому что они существенно используют типы более высокого класса.Итак, работая в Haskell,
class MonadTrans (t :: (* -> *) -> (* -> *)) where
lift :: Monad m => m a -> t m a
transform :: Monad m :- Monad (t m)
Давайте пройдемся по этой строке за строкой.В первой строке объявляется, что преобразователь монад представляет собой тип t
, который принимает аргумент вида * -> *
(то есть тип, ожидающий один аргумент) и превращает его в другой тип * -> *
.Когда вы понимаете, что все монады имеют вид * -> *
, вы можете видеть, что намерение состоит в том, что t
превращает монады в другие монады.
Следующая строка говорит, что все преобразователи монад должны поддерживать операцию lift
, которая берет произвольную монаду m
и поднимает ее в мир трансформатора t m
.
Наконец, метод transform
говорит, что для любой монады m
, t m
также должна быть монадой.Я использую оператор entailment :-
из пакет constraints
.
Это станет более понятным на примере.Вот преобразователь монад, который добавляет Maybe
-ность к произвольной базовой монаде m
.Оператор nothing
позволяет нам прервать вычисление.
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
nothing :: Monad m => MaybeT m a
nothing = MaybeT (return Nothing)
Чтобы MaybeT
был преобразователем монад, он должен быть монадой всякий раз, когда его аргумент является монадой.
instance Monad m => Monad (MaybeT m) where
return = MaybeT . return . Just
MaybeT m >>= f = MaybeT $ m >>= maybe (return Nothing) (runMaybeT . f)
Теперь, чтобы написать реализацию MonadTrans
.Реализация lift
оборачивает возвращаемое значение базовой монады в Just
.Реализация transform
неинтересна;он просто говорит решателю ограничений GHC проверять, что MaybeT
действительно является монадой, когда ее аргумент равен.
instance MonadTrans MaybeT where
lift = MaybeT . fmap Just
transform = Sub Dict
Теперь мы можем написать монадическое вычисление, которое использует MaybeT
, чтобы добавить сбой, например,State
монада.lift
позволяет нам использовать стандартные State
методы get
и put
, но у нас также есть доступ к nothing
, если нам нужно потерпеть неудачу в вычислениях.(Я думал об использовании вашего примера IEnumerable
(он же []
), но есть кое-что извращенное в добавлении сбоя в монаду, которая уже его поддерживает.)
example :: MaybeT (State Int) ()
example = do
x <- lift get
if x < 0
then nothing
else lift $ put (x - 1)
Что делает монадные преобразователи действительно полезными,их наращиваемость.Это позволяет вам составлять большие монады с множеством возможностей из множества маленьких монад с одной способностью каждая.Например, конкретному приложению может потребоваться выполнить ввод-вывод, прочитать переменные конфигурации и выбросить исключения;это будет закодировано с помощью типа
type Application = ExceptT AppError (ReaderT AppConfig IO)
В mtl
пакете есть инструменты, которые помогут вам абстрагироваться от точного набора и порядка монадных преобразователей в данном стеке, позволяя вам отклонять вызовы на lift
.