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?

MVardebería preferirse a esoSTRef?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.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 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).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 anMVares una caja que puede estar llena o vacía. Entonces, donde anIORef Intsiempre tiene unInt(o es inferior), unMVar Intpuede tener unInto puede estar vacío. Si un hilo intenta leer un valor de un vacíoMVar, se bloqueará hasta queMVarse llene (por otro hilo). Básicamente, anMVar aes equivalente a anIORef (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 noIO, unStatemó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 principalSTes theSTRef, que es como unaIORefmónada pero con una mónada diferente. LaSTmó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 queSTpuede 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 elSTcá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 queprintItse definió, pero desea buscar el valor al que está vinculado ese nombre en el entorno en el queprintItse llama (aftersetItpuede 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