La mónada lectora es tan compleja y parece inútil. En un lenguaje imperativo como Java o C ++, no existe un concepto equivalente para la mónada del lector, si no me equivoco.
¿Puedes darme un ejemplo sencillo y aclarar esto un poco?
haskell
monads
reader-monad
chipbk10
fuente
fuente
Respuestas:
¡No tengas miedo! La mónada del lector en realidad no es tan complicada y tiene una utilidad realmente fácil de usar.
Hay dos formas de abordar una mónada: podemos preguntar
Desde el primer enfoque, la mónada lectora es un tipo abstracto
tal que
Entonces, ¿cómo usamos esto? Bueno, la mónada lectora es buena para pasar información de configuración (implícita) a través de un cálculo.
Siempre que tenga una "constante" en un cálculo que necesite en varios puntos, pero realmente le gustaría poder realizar el mismo cálculo con diferentes valores, entonces debería usar una mónada de lectura.
Las mónadas lectoras también se utilizan para hacer lo que la gente de OO llama inyección de dependencia . Por ejemplo, el algoritmo negamax se usa con frecuencia (en formas altamente optimizadas) para calcular el valor de una posición en un juego de dos jugadores. Sin embargo, al algoritmo en sí no le importa en qué juego estás jugando, excepto que debes poder determinar cuáles son las posiciones "siguientes" en el juego, y debes poder saber si la posición actual es una posición de victoria.
Esto funcionará con cualquier juego de dos jugadores finito y determinista.
Este patrón es útil incluso para cosas que no son realmente una inyección de dependencia. Suponga que trabaja en finanzas, podría diseñar una lógica complicada para fijar el precio de un activo (por ejemplo, un derivado), lo cual está muy bien y puede hacerlo sin mónadas apestosas. Pero luego, modifica su programa para manejar múltiples monedas. Necesita poder convertir entre monedas sobre la marcha. Su primer intento es definir una función de nivel superior
para obtener precios al contado. A continuación, puede llamar a este diccionario en su código ... ¡pero espere! ¡Eso no funcionará! El diccionario de divisas es inmutable y, por lo tanto, debe ser el mismo no solo durante la vida útil de su programa, ¡sino desde el momento en que se compila ! Entonces, ¿Qué haces? Bueno, una opción sería usar la mónada Reader:
Quizás el caso de uso más clásico es la implementación de intérpretes. Pero, antes de ver eso, necesitamos introducir otra función
Bien, entonces Haskell y otros lenguajes funcionales se basan en el cálculo lambda . El cálculo lambda tiene una sintaxis similar a
y queremos escribir un evaluador para este idioma. Para hacerlo, necesitaremos realizar un seguimiento de un entorno, que es una lista de enlaces asociados con términos (en realidad, serán cierres porque queremos hacer un alcance estático).
Cuando hayamos terminado, deberíamos obtener un valor (o un error):
Entonces, escribamos el intérprete:
Finalmente, podemos usarlo pasando un entorno trivial:
Y eso es todo. Un intérprete completamente funcional para el cálculo lambda.
La otra forma de pensar en esto es preguntarse: ¿Cómo se implementa? La respuesta es que la mónada lectora es en realidad una de las mónadas más simples y elegantes.
¡Lector es solo un nombre elegante para funciones! Ya lo hemos definido,
runReader
entonces, ¿qué pasa con las otras partes de la API? Bueno, cadaMonad
es también unFunctor
:Ahora, para obtener una mónada:
que no da tanto miedo.
ask
es realmente simple:mientras
local
que no es tan malo:Bien, entonces la mónada del lector es solo una función. ¿Por qué tiene Reader en absoluto? Buena pregunta. En realidad, ¡no lo necesitas!
Estos son aún más simples. Además,
ask
es justaid
ylocal
es sólo composición de funciones con el orden de las funciones cambiado.fuente
Reader
es una función con alguna implementación particular de la clase de tipo mónada? Decirlo antes me habría ayudado a desconcertarme un poco menos. Primero no lo entendía. A mitad de camino pensé "Oh, te permite devolver algo que te dará el resultado deseado una vez que proporciones el valor faltante". Pensé que era útil, pero de repente me di cuenta de que una función hace exactamente esto.local
embargo, la función necesita más explicación ..(Reader f) >>= g = (g (f x))
?x
?Recuerdo estar desconcertado como tú, hasta que descubrí por mi cuenta que las variantes de la mónada Lectora están en todas partes. . ¿Cómo lo descubrí? Porque seguí escribiendo código que resultó ser pequeñas variaciones.
Por ejemplo, en un momento estaba escribiendo un código para tratar con valores históricos ; valores que cambian con el tiempo. Un modelo muy simple de esto son las funciones desde puntos de tiempo hasta el valor en ese momento:
La
Applicative
instancia significa que si tieneemployees :: History Day [Person]
ycustomers :: History Day [Person]
puede hacer esto:Es decir,
Functor
yApplicative
nos permiten adaptar funciones regulares, no históricas, para trabajar con historias.La instancia de la mónada se entiende más intuitivamente considerando la función
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
. Una función de tipoa -> History t b
es una función que asigna unaa
a un historial deb
valores; por ejemplo, podría tenergetSupervisor :: Person -> History Day Supervisor
ygetVP :: Supervisor -> History Day VP
. Entonces, la instancia de Monad paraHistory
se trata de componer funciones como estas; por ejemplo,getSupervisor >=> getVP :: Person -> History Day VP
es la función que obtiene, para cualquieraPerson
, el historial deVP
correos electrónicos que ha tenido.Bueno, esta
History
mónada es exactamente igual queReader
.History t a
es realmente lo mismo queReader t a
(que es lo mismo quet -> a
).Otro ejemplo: he estado creando prototipos de diseños OLAP en Haskell recientemente. Una idea aquí es la de un "hipercubo", que es un mapeo de las intersecciones de un conjunto de dimensiones a valores. Aquí vamos de nuevo:
Una operación común en hipercubos es aplicar funciones escalares de múltiples lugares a los puntos correspondientes de un hipercubo. Esto lo podemos conseguir definiendo una
Applicative
instancia paraHypercube
:Acabo de copiar el
History
código anterior y cambiar los nombres. Como puedes ver,Hypercube
también es justoReader
.Lo sigue y sigue. Por ejemplo, los intérpretes de idiomas también se reducen a
Reader
, cuando aplica este modelo:Reader
ask
Reader
entorno de ejecución.local
Una buena analogía es que a
Reader r a
representa unaa
con "agujeros" que le impiden saber de quéa
estamos hablando. Solo puede obtener un reala
una vez que proporcione unr
para rellenar los agujeros. Hay toneladas de cosas así. En los ejemplos anteriores, un "historial" es un valor que no se puede calcular hasta que se especifica una hora, un hipercubo es un valor que no se puede calcular hasta que se especifica una intersección y una expresión de lenguaje es un valor que se puede No se calculará hasta que proporcione los valores de las variables. También te da una idea de por quéReader r a
es lo mismo quer -> a
, porque esa función también es intuitivamente unaa
faltar
.Por lo tanto
Functor
, las instanciasApplicative
yMonad
deReader
son una generalización muy útil para los casos en los que modela algo del tipo "a
y le falta unr
" y le permite tratar estos objetos "incompletos" como si estuvieran completos.Otra forma más de decir lo mismo: a
Reader r a
es algo que consumer
y producea
, y las instanciasFunctor
,Applicative
yMonad
son patrones básicos para trabajar conReader
s.Functor
= hacer unaReader
que modifique la salida de otraReader
;Applicative
= conectar dosReader
sa la misma entrada y combinar sus salidas;Monad
= inspeccionar el resultado de aReader
y usarlo para construir otroReader
. Las funcioneslocal
ywithReader
= hacen unaReader
que modifica la entrada a otraReader
.fuente
GeneralizedNewtypeDeriving
extensión para derivarFunctor
,Applicative
,Monad
, etc., para Newtypes en función de sus tipos subyacentes.En Java o C ++ puedes acceder a cualquier variable desde cualquier lugar sin ningún problema. Aparecen problemas cuando su código se vuelve multiproceso.
En Haskell, solo tiene dos formas de pasar el valor de una función a otra:
fn1 -> fn2 -> fn3
, la funciónfn2
puede no necesitar parámetro que se pasa defn1
afn3
.La mónada Reader simplemente pasa los datos que desea compartir entre funciones. Las funciones pueden leer esos datos, pero no pueden cambiarlos. Eso es todo lo que hace la mónada Reader. Bueno, casi todos. También hay una serie de funciones como
local
, pero por primera vez solo puedes quedarteasks
.fuente
do
anotaciones de código de 'estilo imperativo' , que sería mejor refactorizar en una función pura.where
cláusula, ¿se aceptará como una tercera forma de pasar variables?