Требуется опыт. Следует помнить, что трансформатор монады ничего не знает о монаде, которое он трансформирует, поэтому внешняя «привязана» к поведению внутреннего. Так
StateT s (ListT m) a
, в первую очередь, недетерминирован вычисление из внутренней монады. Тогда, принимая недетерминизм как нормальный, вы добавляете состояние - то есть каждая «ветвь» недетерминизма будет иметь свое собственное состояние.
Контраст с ListT (StateT s m) a
, что в первую очередь с сохранением состояния - т.е. там будет только одно состояния для всего вычисления (по модулю m
), и вычисление будет действовать «однопоточное» в состоянии, потому что именно State
средства. Недетерминизм будет выше этого - так что ветви смогут наблюдать изменения состояния предыдущих неудавшихся ветвей. (В этой конкретной комбинации это действительно странно, и я никогда не нуждался в ней).
Вот diagram Дэн Piponi, который дает некоторую полезную интуицию:
Я также считаю, что полезно расширить к типу реализации, чтобы дать мне почувствовать, какие вычисления это , ListT
трудно расширить, но вы можете увидеть его как «неопределенный», а StateT
легко расширить. Поэтому для приведенного выше примера я бы посмотрел
StateT s (ListT m) a =~ s -> ListT m (a,s)
I.e. он принимает входящее состояние и возвращает много исходящих состояний. Это дает вам представление о том, как это будет работать. Аналогичный подход заключается в том, чтобы посмотреть тип функции run
, который вам понадобится для вашего стека, - соответствует ли это вашей информации и необходимой информации?
Вот некоторые эмпирические правила. Они не заменяют время, чтобы выяснить, какой из них вам действительно необходим, расширяя и просматривая, но если вы просто ищете «добавление функций» в каком-то императивном смысле, тогда это может быть полезно.
ReaderT
, WriterT
и StateT
являются наиболее распространенными трансформаторами. Во-первых, все они коммутируют друг с другом, поэтому не имеет значения, какой заказ вы их вложите (рассмотрите, используя RWS
, если вы используете все три). Кроме того, на практике я обычно хочу, чтобы они были снаружи, с более богатыми трансформаторами, такими как ListT
, LogicT
и ContT
с внутренней стороны.
ErrorT
и MaybeT
обычно проходят снаружи вышеуказанных трех; давайте посмотрим на то, как MaybeT
взаимодействует с StateT
:
MaybeT (StateT s m) a =~ StateT s m (Maybe a) =~ s -> m (Maybe a, s)
StateT s (MaybeT m) a =~ s -> MaybeT m (a,s) =~ s -> m (Maybe (a,s))
MaybeT
Когда находится на внешней стороне, изменение состояния наблюдается даже тогда, когда вычисление не удается. Когда MaybeT
находится внутри, если вычисление завершилось неудачно, вы не получите состояние, поэтому вам нужно прервать любые изменения состояния, которые произошли при неудачном вычислении. Какой из них вы хотите, зависит от того, что вы пытаетесь сделать - первое, однако, соответствует интуиции императивных программистов. (Не то, что это обязательно нужно для чего-то)
Надеюсь, это дало вам представление о том, как думать о стеках трансформаторов, поэтому у вас есть больше инструментов для анализа того, как должен выглядеть ваш стек. Если вы идентифицируете проблему как монадическое вычисление, получение права монады - одно из самых важных решений, которое нужно сделать, и это не всегда легко. Не торопитесь и изучите возможности.