Возьмем монадный преобразователь MaybeT:
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
Я бы не ожидал другого определения, потому что Maybe — это просто своего рода блок с (необязательным) содержимым (типа a выше), так что же MaybeT может делать с параметром m, если не обертывать все Maybe в нем? Написание :: Maybe (m a) не имело бы смысла, поскольку это всего лишь тип someaction <$> theMaybe.
Но в других случаях мог быть и другой выбор.
Возьмем монадный преобразователь StateT, определяемый как
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
Это определение, если я посмотрю на него в свете определения монады State (как это было бы, если бы оно не было определено поверх StateT через Identity, очевидно),
newtype State s a = State { runState :: s -> (a, s) }
кажется, соответствует интерпретации «функции a -> b как контейнера с содержимым b», именно так я изначально понял, что означает экземпляр Functor(->) r. Итак, справедливо: m оборачивает «содержимое» функции (т. е. «содержимое» вычислений с сохранением состояния).
Но это (или мне кажется!) совершенно отличается от случая MaybeT, где m охватывает не внутреннюю часть Maybe, которая есть a, а всю Maybe a.
Если бы StateT должен был напоминать MaybeT (в данном случае использование m для обертывания всего), это было бы так
newtype StateT' s m a = StateT' { runStateT' :: m (s -> (a,s)) }
По этому поводу приходят на ум некоторые мысли:
IO будет означать, что можно выбрать целое произвольное вычисление с состоянием s -> (a, s) из монады IO, тогда как если фактический StateT накладывается поверх IO, IO можно использовать только для получения нового sсостояния и результата a; но все же я не уверен, что полностью понимаю разницу.И, наконец, что насчет этого?
newtype StateT'' s m a = StateT'' { runStateT'' :: s -> (m a,s) }
Это кажется немного бесполезным, потому что... такое ощущение, что вычисление с сохранением состояния не имеет смысла, потому что, опять же, в примере с m, являющимся IO, a будет происходить от IO, но... О, я действительно не понимаю.
Дело в том, что у меня есть опыт полезности StateT как такового, но я не очень понимаю причины, почему он полезен, и других альтернатив не было бы. Или они бы это сделали?





Чтобы рассмотреть, что может пойти не так, возьмите m = IO и подумайте, какие эффекты могут быть представлены вашими типами, а какие нет.
newtype StateT' s m a = StateT' { runStateT' :: m (s -> (a,s)) }
Ну, это становится IO (s -> (a,s)), поэтому он выполняет эффекты ввода-вывода перед чтением текущего sсостояния для создания нового sсостояния (и результата a).
Таким образом, это не может выразить вычисление «прочитать текущее состояние и распечатать его». Не так уж и полезно.
newtype StateT'' s m a = StateT'' { runStateT'' :: s -> (m a,s) }
Вот и получаем s -> (IO a,s). Из этого легко получить функцию s -> s, которая вычисляет новое состояние в чистом виде. Это означает, что ввод-вывод не может повлиять на новое состояние.
Таким образом, это не может выразить вычисление «запросить у пользователя ввод с клавиатуры и изменить состояние в зависимости от этого». Тоже не так уж и полезно.
Помимо этих проблем, мы должны отметить, что тип, который мы получаем, помещая m в случайные места, может даже не быть монадой! Я не уверен, что мы сможем определить разумный >>= для предложенных вами типов. Попытка сделать это может стать хорошим упражнением, которое может привести к убеждению себя, что это не сработает.
Похоже, что оба эти аппликатива не являются монадами. В первом случае «структура» m-операций фиксирована и на нее не может влиять эволюция состояния. Во втором случае «структура» изменений состояния фиксирована и на нее не могут повлиять m-операции. Как-то аккуратно...
mвставляется в тип. (Хотя отмечу, что все общие из них имеют формуf ∘ m ∘ gдля функторовfиg, поэтому не кажется совершенно произвольным, куда можно вставитьm.)