Qué es la mónada RWS y cuándo se utiliza


Me encontré con la mónada RWS y su monadtransformador mientras buscaba algo en la biblioteca mtl. No hay documentación real allí, y me preguntaba qué es esto y dónde se usa.

He llegado tan lejos como encontrar que RWS es un acrónimo fro Reader, Writer, State y que es una pila de esos tres transformadores mónadas. Lo que no puedo entender es por qué esto es mejor que el Estado por sí mismo.

Author: John F. Miller, 2013-09-20

2 answers

La razón más práctica para esto es para la probabilidad y para firmas de tipo más precisas.

La fuerza clave de haskell es lo bien que puede especificar lo que hace una función a través del sistema de tipos. Compare el tipo c # / java:

public int CSharpFunction(int param) { ...

Con uno de haskell:

someFunction :: Int -> Int

El haskell no solo nos dice los tipos necesarios para los parámetros y el tipo de retorno, sino también lo que la función puede afectar. Por ejemplo, no puede hacer ninguna IO, ni puede leer o cambiar ninguna global estado, o acceder a cualquier dato de configuración estática. Tampoco puede ser cierto para la función c#, pero no podemos decirlo.

Esto es una gran ayuda con las pruebas. Sabemos que lo único que necesitamos probar con haskell someFunction es si obtiene las salidas esperadas para algunas entradas de muestra. No se requiere ninguna configuración posible, y la función nunca dará un resultado diferente para la misma entrada. Esto hace que las pruebas sean bastante simples con funciones puras.


Sin embargo, a menudo una función no puede ser puro. Por ejemplo, puede necesitar acceder a alguna información global solo para leer. Podríamos simplemente añadir otro parámetro a la función:

readerFunc :: GlobalConfig -> Int -> Int

Pero a menudo es más fácil usar una mónada, ya que son más fáciles de componer:

readerFunc2 :: Int -> Reader GlobalConfig Int

Probar esto es casi tan fácil como probar una función pura. Solo necesitamos probar varias permutaciones del valor input Int y el valor de configuración del lector GlobalConfig.

Una función puede necesitar escribir valores (por ejemplo, para tala). Esto también se puede hacer con una mónada:

writerFunc :: Int -> Writer String Int

De nuevo probar esto es casi tan fácil como para una función pura. Solo probamos si para una entrada Int dada, se devuelve el Int apropiado, así como el escritor final correcto String.

Otra función puede necesitar leer y cambiar el estado:

stateFunc :: Int -> State GlobalState Int

Esto es más difícil de probar sin embargo. No solo tenemos que comprobar la salida utilizando varios Ints de entrada y estados globales iniciales, sino que también tenemos que probar si el final GlobalState es el valor correcto.


Ahora considere una función que:

  • Necesita leer desde un tipo de datos ProgramConfig
  • Escribe valores en una cadena para registrar
  • Utilice y modifique un valor ProgramState.

Podríamos hacer algo como esto:

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

Sin embargo, ese tipo no es muy preciso. Implica que el ProgramConfig puede ser cambiado, lo que no lo hará. También implica que la función puede usar el valor pLogs, lo que no lo hará. Además, probarlo es más complejo, ya que teóricamente, la función podría cambiar accidentalmente la configuración del programa, o usar el valor pLogs actual en sus cálculos.

Esto se puede mejorar mucho con esto:

betterFunction :: Int -> RWS ProgramConfig String ProgramState Int

Este tipo es muy preciso. Sabemos que el ProgramConfig solo se lee de. Sabemos que el String solo cambia. La única parte que requiere tanto lectura como escritura es la ProgramState, como se esperaba. Esto es más fácil de probar, ya que solo necesitamos probar diferentes combinaciones de ProgramConfig, ProgramState y Int para la entrada, y comprobar el resultado Int, String y ProgramState para la salida. Sabemos que no podemos cambiar accidentalmente la configuración del programa, o usar el valor actual del registro del programa en nuestros cálculos.


El sistema de tipos haskell está ahí para ayudarnos, debemos darle tanta información como podamos para que pueda detectar errores antes que nosotros.

(Tenga en cuenta que cada tipo de haskell en esta respuesta podría ser el equivalente al tipo de c# en la parte superior, dependiendo de cómo se implementa realmente la función c#).

 27
Author: David Miani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-20 07:36:22

El estado es un concepto muy generalizado y por lo tanto se puede usar para muchas cosas. Lector y escritor se puede pensar en una forma especializada de Estado con algunas restricciones, solo se puede leer de un lector y solo se puede escribir a un escritor. Usando estas formas especializadas de estado puedes ser más explícito sobre lo que estás tratando de lograr o cuáles son exactamente las intenciones.

Otra analogía podría ser usar map / dictionary para modelar cualquier cosa (objetos, datos, controladores de eventos, etc.), pero el uso de una forma más especializada de mapa/diccionario hace las cosas más explícitas

 3
Author: Ankur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-20 06:51:49