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?
MVar
debería preferirse a esoSTRef
?STRef
garantiza 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.Ok, empezaré con
IORef
.IORef
proporciona 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 enIO
. 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).MVar
s son básicamente lo mismo que un IORef, excepto por dos diferencias muy importantes.MVar
es una primitiva de concurrencia, por lo que está diseñada para acceder desde múltiples subprocesos. La segunda diferencia es que anMVar
es una caja que puede estar llena o vacía. Entonces, donde anIORef Int
siempre tiene unInt
(o es inferior), unMVar Int
puede tener unInt
o puede estar vacío. Si un hilo intenta leer un valor de un vacíoMVar
, se bloqueará hasta queMVar
se llene (por otro hilo). Básicamente, anMVar a
es equivalente a anIORef (Maybe a)
con semántica adicional que es útil para la concurrencia.State
es 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 noIO
, 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.ST
es algo ligeramente diferente. La estructura de datos principalST
es theSTRef
, que es como unaIORef
mónada pero con una mónada diferente. LaST
mó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 queST
puede 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 elST
cálculo se ejecutara en un hilo separado con almacenamiento local del hilo.fuente
Otros han hecho las cosas centrales, pero para responder a la pregunta directa:
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úaprintIt
, desea buscar el nombre de x en el entorno inicial en el queprintIt
se definió, pero desea buscar el valor al que está vinculado ese nombre en el entorno en el queprintIt
se llama (aftersetIt
puede 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.
fuente