Esta es una "interpretación" sugerida de la IO
mónada. Si quiere tomar en serio esta "interpretación", entonces debe tomar en serio "RealWorld". Es irrelevante si action world
se evalúa especulativamente o no, action
no tiene ningún efecto secundario, sus efectos, si los hay, se manejan devolviendo un nuevo estado del universo donde se han producido esos efectos, por ejemplo, se ha enviado un paquete de red. Sin embargo, el resultado de la función es ((),world)
y, por lo tanto, el nuevo estado del universo es world
. No usamos el nuevo universo que podríamos haber evaluado especulativamente en el lateral. El estado del universo es world
.
Probablemente tengas dificultades para tomar eso en serio. Hay muchas maneras en que esto es, en el mejor de los casos, superficialmente paradójico y sin sentido. La concurrencia es especialmente no obvia o loca con esta perspectiva.
"Espera, espera", dices. " RealWorld
es solo una 'ficha'. En realidad, no es el estado de todo el universo". Bien, entonces esta "interpretación" no explica nada. Sin embargo, como detalle de implementación , así es como los modelos GHC IO
. 1 Sin embargo, esto significa que tenemos "funciones" mágicas que en realidad tienen efectos secundarios y este modelo no proporciona orientación sobre su significado. Y, dado que estas funciones en realidad tienen efectos secundarios, la preocupación que plantea es completamente acertada. GHC no tiene que salir de su manera de asegurarse RealWorld
y estas funciones especiales no están optimizados de manera que cambien el comportamiento previsto del programa.
Personalmente (como probablemente es evidente ahora), creo que este modelo de "paso del mundo" IO
es simplemente inútil y confuso como herramienta pedagógica. (Si es útil para la implementación, no lo sé. Para GHC, creo que es más un artefacto histórico).
Un enfoque alternativo es ver IO
como solicitudes descriptivas con manejadores de respuestas. Hay varias maneras de hacer esto. Probablemente lo más accesible es usar una construcción de mónada gratis, específicamente podemos usar:
data IO a = Return a | Request OSRequest (OSResponse -> IO a)
Hay muchas maneras de hacer esto más sofisticado y tener propiedades algo mejores, pero esto ya es una mejora. No requiere suposiciones filosóficas profundas sobre la naturaleza de la realidad para entender. Todo lo que dice es que IO
es un programa trivial Return
que no hace nada más que devolver un valor, o es una solicitud al sistema operativo con un controlador para la respuesta. OSRequest
puede ser algo como:
data OSRequest = OpenFile FilePath | PutStr String | ...
Del mismo modo, OSResponse
podría ser algo como:
data OSResponse = Errno Int | OpenSucceeded Handle | ...
(Una de las mejoras que se pueden hacer es hacer que las cosas sean más seguras para que sepa que no recibirá OpenSucceeded
una PutStr
solicitud). Este modelo IO
describe las solicitudes que son interpretadas por algún sistema (para la IO
mónada "real" esto es el tiempo de ejecución de Haskell en sí), y luego, tal vez, ese sistema llamará al controlador que hemos proporcionado una respuesta. Esto, por supuesto, tampoco da ninguna indicación de cómo se PutStr "hello world"
debe manejar una solicitud como , pero tampoco pretende hacerlo. Hace explícito que esto se está delegando a algún otro sistema. Este modelo también es bastante preciso. Todos los programas de usuario en sistemas operativos modernos necesitan hacer solicitudes al sistema operativo para hacer cualquier cosa.
Este modelo proporciona las intuiciones correctas. Por ejemplo, muchos principiantes ven cosas como el <-
operador como "desenvolviendo" IO
, o tienen (desafortunadamente reforzado) vistas de que un IO String
, por ejemplo, es un "contenedor" que "contiene" String
s (y luego <-
los saca). Esta vista de solicitud-respuesta hace que esta perspectiva sea claramente errónea. No hay un identificador de archivo dentro de OpenFile "foo" (\r -> ...)
. Una analogía común para enfatizar esto es que no hay pastel dentro de una receta para pastel (o tal vez "factura" sería mejor en este caso).
Este modelo también funciona fácilmente con concurrencia. Podemos tener fácilmente un constructor para me OSRequest
gusta Fork :: (OSResponse -> IO ()) -> OSRequest
y luego el tiempo de ejecución puede intercalar las solicitudes producidas por este controlador adicional con el controlador normal como quiera. Con cierta inteligencia, puede usar esto (o técnicas relacionadas) para modelar cosas como la concurrencia más directamente en lugar de simplemente decir "hacemos una solicitud al sistema operativo y las cosas suceden". Así es como funciona la IOSpec
biblioteca .
1 Hugs utilizó una implementación basada en la continuación de la IO
cual es más o menos similar a lo que describo, aunque con funciones opacas en lugar de un tipo de datos explícito. HBC también usó una implementación basada en la continuación en capas sobre el antiguo IO basado en el flujo de solicitud-respuesta. NHC (y, por lo tanto, YHC) usaba thunks, es decir, aproximadamente, IO a = () -> a
aunque ()
se llamaba World
, pero no está haciendo pasar el estado. JHC y UHC utilizaron básicamente el mismo enfoque que GHC.