Есть ли существенная разница между StateT через Reader и ReaderT через State? - PullRequest
4 голосов
/ 02 июня 2019

Когда я проектирую свою модель программирования, у меня всегда возникает дилемма, какой подход лучше:

type MyMonad1 = StateT MyState (Reader Env)
type MyMonad2 = ReaderT Env (State MyState)

Каковы преимущества и компромиссы между использованием одной монады над другой?Это имеет значение вообще?Как насчет производительности?

1 Ответ

6 голосов
/ 03 июня 2019

В общем случае разные упорядочения монадных преобразователей приведут к разному поведению, но, как было отмечено в комментариях, для двух упорядочений «состояние» и «читатель» мы имеем следующие изоморфизмы вплоть до новых типов:

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) выглядит для меня более естественным.

...