Подтвердить что ты не робот

Что такое RWS Monad и когда он используется

Я наткнулся на RWS Monad и его MonadTransformer, когда искал что-то в библиотеке mtl. Там нет реальной документации, и мне было интересно, что это такое и где оно используется.

Я догадался, что RWS - это аббревиатура от Reader, Writer, State, и это стек этих трех монадных трансформаторов. Я не могу понять, почему это лучше, чем само по себе.

4b9b3361

Ответ 1

Наиболее практичная причина этого - тестируемость и более точные сигнатуры типов.

Ключевой силой haskell является то, насколько хорошо вы можете указать, что делает функция через систему типов. Сравните тип С#/java:

public int CSharpFunction(int param) { ...

с haskell one:

someFunction :: Int -> Int

The haskell не только сообщает нам типы, необходимые для параметров и типа возврата, но также и то, что может повлиять на функцию. Например, он не может выполнять никаких операций ввода-вывода и не может читать или изменять какое-либо глобальное состояние или получать доступ к статическим данным конфигурации. Для функции С# также не может быть прав, но мы не можем сказать.

Это отличная помощь при тестировании. Мы знаем, что единственное, что нам нужно проверить с помощью haskell someFunction, - это получить ожидаемые выходные данные для некоторых выборочных входов. Никакой возможной установки не требуется, и функция никогда не даст другого результата для одного и того же ввода. Это делает тестирование довольно простым с чистыми функциями.


Однако часто функция не может быть чистой. Например, для чтения может потребоваться доступ к некоторой глобальной информации. Мы могли бы просто добавить еще один параметр к функции:

readerFunc :: GlobalConfig -> Int -> Int

Но часто бывает проще использовать монаду, так как они легче сочинять:

readerFunc2 :: Int -> Reader GlobalConfig Int

Тестирование это почти так же просто, как тестирование чистой функции. Нам просто нужно проверить различные перестановки входного значения Int и значение конфигурации считывателя GlobalConfig.

Возможно, функция должна выписать значения (например, для ведения журнала). Это также можно сделать с помощью монады:

writerFunc :: Int -> Writer String Int

Опять тестирование это почти так же просто, как для чистой функции. Мы просто проверяем, если для данного ввода Int возвращается соответствующий Int, а также правый конечный писатель String.

Другая функция, возможно, потребуется прочитать и изменить состояние:

stateFunc :: Int -> State GlobalState Int

Это сложнее проверить. Мы не только должны проверять вывод, используя различные входные данные Ints и начальные GlobalStates, но нам также нужно проверить, является ли конечное значение GlobalState правильным.


Теперь рассмотрим функцию, которая:

  • Требуется прочитать из типа данных ProgramConfig
  • Запись значений в строку для ведения журнала
  • Использовать и изменять значение ProgramState.

Мы могли бы сделать что-то вроде этого:

data ProgramData = ProgramData { pState :: ProgramState, pConfig :: ProgramConfig, pLogs :: String }
complexFunction :: Int -> State ProgramData Int

Однако этот тип не очень точен. Это означает, что ProgramConfig может быть изменен, чего не будет. Это также означает, что функция может использовать значение pLogs, чего не будет. Кроме того, тестирование является более сложным, поскольку теоретически функция может случайно изменить конфигурацию программы или использовать текущие значения pLogs в своих вычислениях.

Это может быть значительно улучшено с помощью этого:

betterFunction :: Int -> RWS ProgramConfig String ProgramState Int

Этот тип очень точный. Мы знаем, что ProgramConfig только читается. Мы знаем, что String только изменилось. Единственная часть, которая требует как чтения, так и записи, - это ProgramState, как и ожидалось. Это проще проверить, так как нам нужно только проверить различные комбинации ProgramConfig, ProgramState и Int для ввода и проверить выходные данные Int, String и ProgramState для вывода. Мы знаем, что мы не можем случайно изменить конфигурацию программы или использовать текущее значение журнала программы в наших вычислениях.


Система типа haskell должна помочь нам, мы должны предоставить ей столько информации, сколько можем, чтобы она могла ловить ошибки до того, как мы это сделаем.

(Обратите внимание, что каждый тип haskell в этом ответе может быть эквивалентным типу С# вверху, в зависимости от того, как фактически реализована функция С#).

Ответ 2

Состояние - очень обобщенная концепция и, следовательно, может использоваться для многих вещей. Читателя и писателя можно рассматривать как специализированную форму государства с некоторыми ограничениями, вы можете читать только читателя, и вы можете писать только писателю. Используя эту специализированную форму состояния, вы можете быть более ясными о том, чего вы пытаетесь достичь, или о том, какие именно намерения.

Другая аналогия может заключаться в использовании карты/словаря для моделирования чего-либо (объектов, данных, обработчиков событий и т.д.), но использование более специализированной формы карты/словаря делает вещи более явными