В общем случае разные упорядочения монадных преобразователей приведут к разному поведению, но, как было отмечено в комментариях, для двух упорядочений «состояние» и «читатель» мы имеем следующие изоморфизмы вплоть до новых типов:
StateT MyState (Reader Env) a ~ MyState -> Env -> (a, MyState)
ReaderT Env (State MyState) a ~ Env -> MyState -> (a, MyState)
, поэтому единственное различие заключается в порядке аргументов, и эти две монады в остальном семантически эквивалентны.
Что касается производительности, то трудно точно знать без сравнения действительного кода.Однако, как одна точка данных, если вы рассмотрите следующее монадическое действие:
foo :: StateT Double (Reader Int) Int
foo = do
n <- ask
modify (* fromIntegral n)
gets floor
, то при компиляции с GHC 8.6.4 с использованием -O2
новые типы - очевидно - оптимизируются, и этогенерирует точно того же ядра, если вы измените сигнатуру на:
foo :: ReaderT Int (State Double) Int
за исключением того, что два аргумента foo
перевернуты.Таким образом, нет никакой разницы в производительности, по крайней мере, в этом простом примере.
В стилистическом плане вы можете столкнуться с ситуациями, когда один порядок приводит к более приятному виду кода, чем другой, но обычно не так уж многовыбирать между ними.В частности, основные монадические действия, подобные приведенному выше, будут выглядеть одинаково с любым порядком.
Без всякой уважительной причины я склоняюсь к # 2, в основном потому, что Env -> MyState -> (a, MyState)
выглядит для меня более естественным.