Я нахожу это нелогичным: даже если ErrorT якобы упаковывает IO, похоже, что информация об ошибке была введена в тип результата действия IO.
Трансформаторы монад обычно не "оборачивают" монаду, к которой они применяются, по крайней мере, в каком-то очевидном смысле. Думая об этом как о «обертывании», я бы подумал, что состав функтора , что именно здесь не происходит.
Для иллюстрации, состав функтора для State s
и Maybe
с расширенными определениями будет выглядеть следующим образом:
newtype StateMaybe s a = StateMaybe (s -> (Maybe a, s)) -- == State s (Maybe a)
newtype MaybeState s a = MaybeState (Maybe (s -> (a, s))) -- == Maybe (State s a)
Обратите внимание, что в первом случае State
ведет себя нормально, а Nothing
не влияет на значение состояния; во втором случае у нас либо простая функция State
, либо ничего вообще. Ни в том, ни в другом случае характерное поведение двух монад на самом деле не объединяет . Это не должно вызывать удивления, поскольку, в конце концов, это то же самое, что вы получите, просто имея значения, использующие одну монаду в качестве обычных значений, используемых внутри другой.
Сравните это с StateT s Maybe
:
newtype StateTMaybe s a = StateTMaybe (s -> Maybe (a, s))
В этом случае оба переплетаются; все идет обычным образом для State
, если только мы не нажмем Nothing
, и в этом случае вычисление будет прервано. Это принципиально отличается от вышеописанных случаев, поэтому монадные трансформаторы даже существуют в первую очередь - для их составления наивно не требуется никакого специального механизма, поскольку они работают независимо друг от друга.
Что касается понимания того, кто находится «снаружи», то это может помочь думать о «внешнем» преобразователе как о том, чье поведение имеет «приоритет», в некотором смысле, когда имеешь дело со значениями в монада, тогда как «внутренняя» монада видит бизнес только как обычно. Обратите внимание, что именно поэтому IO
всегда является самым внутренним - оно не позволяет чему-то еще подняться в своем бизнесе, тогда как гипотетический IOT
преобразователь будет вынужден позволить обернутой монаде тянуть все виды махинаций, например, дублирование или выбросить RealWorld
токен.
StateT
и ReaderT
оба помещают «внутреннюю» монаду вокруг результата функции; Вы должны указать значение состояния или среду перед тем, как попасть в преобразованную монаду.
MaybeT
и ErrorT
оба проскальзывают внутри преобразованной монады, гарантируя, что она может вести себя обычным образом, за исключением значения, которое может отсутствовать.
Writer
полностью пассивен и просто привязывается к значениям в монаде, поскольку он совсем не влияет на поведение.
ContT
хранит вещи при себе, откладывая обращение с преобразованной монадой в целом, оборачивая только тип result .
Это немного волнисто, но эх, монадные трансформеры являются своего рода специальными и запутанными, увы. Я не знаю, есть ли какое-то аккуратное теоретическое обоснование для того или иного сделанного выбора, кроме того факта, что они работают и делают то, что вы обычно хотите, чтобы комбинация (а не композиция) двух монад делаем.
Следовательно, мне трудно думать о том, что означает объединять одну монаду с другой, даже когда у меня нет проблем с пониманием того, что каждая монада означает индивидуально.
Да, это похоже на то, чего ожидать, я боюсь.