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,
runReaderentonces, ¿qué pasa con las otras partes de la API? Bueno, cadaMonades también unFunctor:Ahora, para obtener una mónada:
que no da tanto miedo.
askes realmente simple:mientras
localque 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,
askes justaidylocales sólo composición de funciones con el orden de las funciones cambiado.fuente
Readeres 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.localembargo, 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
Applicativeinstancia significa que si tieneemployees :: History Day [Person]ycustomers :: History Day [Person]puede hacer esto:Es decir,
FunctoryApplicativenos 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 bes una función que asigna unaaa un historial debvalores; por ejemplo, podría tenergetSupervisor :: Person -> History Day SupervisorygetVP :: Supervisor -> History Day VP. Entonces, la instancia de Monad paraHistoryse trata de componer funciones como estas; por ejemplo,getSupervisor >=> getVP :: Person -> History Day VPes la función que obtiene, para cualquieraPerson, el historial deVPcorreos electrónicos que ha tenido.Bueno, esta
Historymónada es exactamente igual queReader.History t aes 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
Applicativeinstancia paraHypercube:Acabo de copiar el
Historycódigo anterior y cambiar los nombres. Como puedes ver,Hypercubetambién es justoReader.Lo sigue y sigue. Por ejemplo, los intérpretes de idiomas también se reducen a
Reader, cuando aplica este modelo:ReaderaskReaderentorno de ejecución.localUna buena analogía es que a
Reader r arepresenta unaacon "agujeros" que le impiden saber de quéaestamos hablando. Solo puede obtener un realauna vez que proporcione unrpara 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 aes lo mismo quer -> a, porque esa función también es intuitivamente unaafaltar.Por lo tanto
Functor, las instanciasApplicativeyMonaddeReaderson una generalización muy útil para los casos en los que modela algo del tipo "ay 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 aes algo que consumery producea, y las instanciasFunctor,ApplicativeyMonadson patrones básicos para trabajar conReaders.Functor= hacer unaReaderque modifique la salida de otraReader;Applicative= conectar dosReadersa la misma entrada y combinar sus salidas;Monad= inspeccionar el resultado de aReadery usarlo para construir otroReader. Las funcioneslocalywithReader= hacen unaReaderque modifica la entrada a otraReader.fuente
GeneralizedNewtypeDerivingextensió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ónfn2puede no necesitar parámetro que se pasa defn1afn3.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
doanotaciones de código de 'estilo imperativo' , que sería mejor refactorizar en una función pura.wherecláusula, ¿se aceptará como una tercera forma de pasar variables?