Diferencia entre State, ST, IORef y MVar

91

Estoy trabajando en Escriba usted mismo un esquema en 48 horas (estoy hasta aproximadamente 85 horas) y he llegado a la parte sobre cómo agregar variables y asignaciones . Hay un gran salto conceptual en este capítulo, y desearía que se hubiera hecho en dos pasos con una buena refactorización en el medio en lugar de saltar directamente a la solución final. De todas formas…

He conseguido perdido con un número de diferentes clases que parecen servir al mismo propósito: State, ST, IORef, y MVar. Los tres primeros se mencionan en el texto, mientras que el último parece ser la respuesta preferida a muchas preguntas de StackOverflow sobre los tres primeros. Todos parecen tener un estado entre invocaciones consecutivas.

¿Cuáles son cada uno de estos y en qué se diferencian unos de otros?


En particular, estas oraciones no tienen sentido:

En su lugar, usamos una función llamada subprocesos de estado , lo que permite que Haskell administre el estado agregado por nosotros. Esto nos permite tratar las variables mutables como lo haríamos en cualquier otro lenguaje de programación, usando funciones para obtener o establecer variables.

y

El módulo IORef le permite utilizar variables con estado dentro de la mónada IO .

Todo esto hace que la línea sea type ENV = IORef [(String, IORef LispVal)]confusa, ¿por qué la segunda IORef? ¿Qué se romperá si escribo en su type ENV = State [(String, LispVal)]lugar?

John F. Miller
fuente

Respuestas:

119

La mónada estatal: un modelo de estado mutable

La mónada de estado es un entorno puramente funcional para programas con estado, con una API simple:

  • obtener
  • poner

Documentación en el paquete mtl .

La mónada de estado se usa comúnmente cuando se necesita estado en un solo hilo de control. En realidad, no usa un estado mutable en su implementación. En cambio, el programa está parametrizado por el valor del estado (es decir, el estado es un parámetro adicional a todos los cálculos). El estado solo parece estar mutado en un único hilo (y no se puede compartir entre hilos).

La mónada ST y STRefs

La mónada ST es la prima restringida de la mónada IO.

Permite un estado mutable arbitrario , implementado como memoria mutable real en la máquina. La API se hace segura en programas sin efectos secundarios, ya que el parámetro de tipo de rango 2 evita que los valores que dependen del estado mutable escapen del alcance local.

Por tanto, permite una mutabilidad controlada en programas que de otro modo serían puros.

Se usa comúnmente para matrices mutables y otras estructuras de datos que se mutan y luego se congelan. También es muy eficiente, ya que el estado mutable es "acelerado por hardware".

API principal:

  • Control.Monad.ST
  • runST: inicia un nuevo cálculo de efecto de memoria.
  • Y STRefs : punteros a células mutables (locales).
  • Las matrices basadas en ST (como vector) también son comunes.

Piense en ello como el hermano menos peligroso de la mónada IO. O IO, donde solo puede leer y escribir en la memoria.

IORef: STRefs en IO

Estos son STRefs (ver arriba) en la mónada IO. No tienen las mismas garantías de seguridad que STRefs sobre la localidad.

MVars: IORefs con cerraduras

Como STRefs o IORefs, pero con un candado adjunto, para un acceso simultáneo seguro desde múltiples subprocesos. IORefs y STRefs solo son seguros en una configuración de subprocesos múltiples cuando se usan atomicModifyIORef(una operación atómica de comparación e intercambio). Los MVars son un mecanismo más general para compartir de forma segura estados mutables.

Generalmente, en Haskell, use MVars o TVars (celdas mutables basadas en STM), sobre STRef o IORef.

Don Stewart
fuente
3
¿Qué significa la M en MVars y T en TVars? Supongo que "Mutable", "Transaccional". Es interesante cómo ST significa State Thread.
CMCDragonkai
10
¿Por qué dices que MVardebería preferirse a eso STRef? STRefgarantiza que solo un subproceso puede mutarlo (y que no pueden ocurrir otros tipos de IO); seguramente eso es mejor si no necesito acceso simultáneo al estado mutable.
Benjamin Hodgson
@CMCDragonkai Siempre asumí que la M significa mutex, pero no puedo encontrarlo documentado en ninguna parte.
Andrew Thaddeus Martin
37

Ok, empezaré con IORef. IORefproporciona un valor que es mutable en la mónada IO. Es solo una referencia a algunos datos y, como cualquier referencia, hay funciones que le permiten cambiar los datos a los que se refiere. En Haskell, todas esas funciones operan en IO. Puede pensar en él como una base de datos, archivo u otro almacén de datos externo; puede obtener y configurar los datos en él, pero hacerlo requiere pasar por IO. La razón por la que IO es necesaria es porque Haskell es puro ; el compilador necesita una forma de saber a qué datos apunta la referencia en un momento dado (lea la publicación de blog "Podrías haber inventado las mónadas" de sigfpe).

MVars son básicamente lo mismo que un IORef, excepto por dos diferencias muy importantes. MVares una primitiva de concurrencia, por lo que está diseñada para acceder desde múltiples subprocesos. La segunda diferencia es que an MVares una caja que puede estar llena o vacía. Entonces, donde an IORef Intsiempre tiene un Int(o es inferior), un MVar Intpuede tener un Into puede estar vacío. Si un hilo intenta leer un valor de un vacío MVar, se bloqueará hasta que MVarse llene (por otro hilo). Básicamente, an MVar aes equivalente a an IORef (Maybe a)con semántica adicional que es útil para la concurrencia.

Statees una mónada que proporciona un estado mutable, no necesariamente con IO. De hecho, es particularmente útil para cálculos puros. Si tiene un algoritmo que usa estado pero no IO, unState mónada suele ser una solución elegante.

También hay una versión transformador mónada de Estado, StateT. Esto se usa con frecuencia para contener datos de configuración de programas o tipos de estado de "juego-mundo-estado" en aplicaciones.

STes algo ligeramente diferente. La estructura de datos principal STes the STRef, que es como una IORefmónada pero con una mónada diferente. La STmónada usa el engaño del sistema de tipos (los "hilos de estado" que mencionan los documentos) para garantizar que los datos mutables no puedan escapar de la mónada; es decir, cuando ejecuta un cálculo ST, obtiene un resultado puro. La razón por la que ST es interesante es que es una mónada primitiva como IO, lo que permite que los cálculos realicen manipulaciones de bajo nivel en bytearrays y punteros. Esto significa que STpuede proporcionar una interfaz pura mientras se utilizan operaciones de bajo nivel en datos mutables, lo que significa que es muy rápido. Desde la perspectiva del programa, es como si el STcálculo se ejecutara en un hilo separado con almacenamiento local del hilo.

Juan L
fuente
17

Otros han hecho las cosas centrales, pero para responder a la pregunta directa:

Todo esto hace que el tipo de línea sea ENV = IORef [(String, IORef LispVal)] confuso. ¿Por qué el segundo IORef? ¿Qué se romperá si lo hago en su type ENV = State [(String, LispVal)]lugar?

Lisp es un lenguaje funcional con estado mutable y alcance léxico. Imagina que has cerrado una variable mutable. Ahora tiene una referencia a esta variable dentro de alguna otra función, digamos (en pseudocódigo estilo haskell) (printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y). Ahora tiene dos funciones: una imprime x y la otra establece su valor. Cuando evalúa printIt, desea buscar el nombre de x en el entorno inicial en el que printItse definió, pero desea buscar el valor al que está vinculado ese nombre en el entorno en el que printItse llama (after setItpuede haber sido llamado cualquier número de veces ).

Hay formas en las que los dos IORefs pueden hacer esto, pero ciertamente necesita más que el último tipo que ha propuesto, lo que no le permite alterar los valores a los que están vinculados los nombres de una manera léxica. Busca en Google el "problema de los funargs" para encontrar una gran cantidad de prehistoria interesante.

sclv
fuente