Pregunta

Alguien podría dar algunos consejos sobre por qué el impuro cálculos en Haskell se modelan como mónadas?

Me refiero a la mónada es simplemente una interfaz con 4 operaciones, entonces, ¿cuál fue el razonamiento a la modelización de efectos secundarios en ella?

¿Fue útil?

Solución

Supongamos que una función tiene efectos secundarios. Si tomamos todos los efectos que produce como parámetros de entrada y de salida, entonces la función es puro al mundo exterior.

Así que para una función impura

f' :: Int -> Int

añadimos el Mundo Real a la consideración

f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.

A continuación, f es puro otra vez. Se define un tipo de datos IO a = RealWorld -> (a, RealWorld) parametrizada, por lo que no es necesario que escriba un mundo real tantas veces

f :: Int -> IO Int

Para el programador, el manejo de una RealWorld directamente es demasiado peligroso, en particular, si un programador pone sus manos en un valor de tipo RealWorld, podrían tratar de copia , lo cual es básicamente imposible. (Pensar en tratar de copiar todo el sistema de archivos, por ejemplo. ¿Dónde lo pondrías?) Por lo tanto, nuestra definición de IO encapsula los estados de todo el mundo también.

Estas funciones impuras son inútiles si no les podemos encadenar. Considere

getLine :: IO String               = RealWorld -> (String, RealWorld)
getContents :: String -> IO String = String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO ()        = String -> RealWorld -> ((), RealWorld)

Queremos obtener un nombre de archivo de la consola, leer ese archivo, a continuación, imprimir el contenido a cabo. ¿Cómo podemos hacerlo si podemos acceder a los estados del mundo real?

printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
                       (contents, world2) = (getContents filename) world1 
                   in  (putStrLn contents) world2 -- results in ((), world3)

Vemos un patrón aquí: las funciones se llaman así:

...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...

Así podríamos definir un operador ~~~ atarlos:

(~~~) :: (IO b) -> (b -> IO c) -> IO c

(~~~) ::      (RealWorld -> (b, RealWorld))
      -> (b -> RealWorld -> (c, RealWorld))
      ->       RealWorld -> (c, RealWorld)
(f ~~~ g) worldX = let (resF, worldY) = f worldX in
                        g resF worldY

entonces podríamos simplemente escriba

printFile = getLine ~~~ getContents ~~~ putStrLn

sin tocar el mundo real.


Ahora supongamos que queremos hacer que el contenido del archivo en mayúsculas también. Uppercasing es una pura función

upperCase :: String -> String

Pero para que sea en el mundo real, que tiene que devolver un IO String. Es fácil de levantar dicha función:

impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)

Esto se puede generalizar:

impurify :: a -> IO a

impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)

para que impureUpperCase = impurify . upperCase, y podemos escribir

printUpperCaseFile = 
    getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn

(Nota: Normalmente escribimos getLine ~~~ getContents ~~~ (putStrLn . upperCase))


Ahora vamos a ver lo que hemos hecho:

  1. Se define un operador (~~~) :: IO b -> (b -> IO c) -> IO c el que las cadenas de dos funciones impuras juntos
  2. Se definió un impurify :: a -> IO a función que convierte un valor puro para impura.

Ahora hacemos el (>>=) = (~~~) identificación y return = impurify, y ver? Tenemos una mónada.


(Para comprobar si realmente es una mónada hay pocos axiomas deben ser satisfechas:

(1) return a >>= f = f a

  impurify a               = (\world -> (a, world))
 (impurify a ~~~ f) worldX = let (resF, worldY) = (\world -> (a, world)) worldX 
                             in f resF worldY
                           = let (resF, worldY) =            (a, worldX))       
                             in f resF worldY
                           = f a worldX

(2) f >>= return = f

  (f ~~~ impurify) a worldX = let (resF, worldY) = impuify a worldX 
                              in f resF worldY
                            = let (resF, worldY) = (a, worldX)     
                              in f resF worldY
                            = f a worldX

(3) f >>= (\x -> g x >>= h) = (f >>= g) >>= h

Ejercicio.)

Otros consejos

  

¿Puede alguien dar algunas indicaciones sobre por qué los cálculos unpure en Haskell se modelan como mónadas?

Esta pregunta contiene un malentendido generalizado. La impureza y la mónada son nociones independientes. La impureza es no modelada por Mónada. Más bien, hay algunos tipos de datos, tales como IO, que representan el cálculo imprescindible. Y para algunos de esos tipos, una pequeña fracción de su interfaz se corresponde con el patrón de interfaz llamada "mónada". Por otra parte, no se conoce ningún explicación puro / functional / denotativo de IO (y no es improbable que sea una, teniendo en cuenta la "sin bin" propósito de IO), aunque no es la historia comúnmente hablado de World -> (a, World) ser el significado de IO a. Esa historia no se puede describir con veracidad IO, porque IO soporta concurrencia y no determinismo. La historia no funciona ni siquiera cuando en los cálculos deterministas que permiten la interacción mediados de cómputo con el mundo.

Para una explicación más detallada, consulte esta respuesta .

Editar : Al volver a leer la pregunta, no creo que mi respuesta es bastante a la pista. Modelos de computación imperativo hacer a menudo resultan ser mónadas, al igual que la cuestión dijo. El autor de la pregunta realmente no podría asumir que monadness de ninguna manera permite el modelado de cómputo imprescindible.

A mi entender, alguien llama Eugenio Moggi notó por primera vez que una construcción matemática previamente desconocido llamado "mónada" podría ser usado para modelar los efectos secundarios en los lenguajes de programación, y por lo tanto especificar su semántica mediante cálculo lambda. Cuando Haskell estaba siendo desarrollado había diversas formas en que los cálculos impuras se modelaron (véase Simon Peyton Jones' '' papel cilicio para más detalles), pero cuando Phil Wadler introdujo mónadas rápidamente se hizo evidente que esta era la respuesta. Y el resto es historia.

  

¿Puede alguien dar algunas indicaciones sobre por qué los cálculos unpure en Haskell se modelan como mónadas?

Bueno, porque Haskell es pura . Es necesario un concepto matemático de distinguir entre cálculos unpure y puros en Tipo de nivel y modelar fluye Programm en respectivamente.

Esto significa que usted tiene que terminar con algún tipo IO a que los modelos de cálculo de un impuro. Luego hay que conocer los caminos de combinando estos cómputos de los cuales aplicar de forma secuencial (>>=) y levante un valor (return) son los más evidentes y los básicos.

Con estos dos, que ya hemos definido una mónada (sin ni siquiera pensar en ello);)

Además, las mónadas proporcionan abstracciones muy generales y potentes , por lo que muchos tipos de flujo de control pueden ser convenientemente generalizarse en funciones monádicos como sequence, liftM o sintaxis especial, por lo que no unpureness un caso especial.

en la programación funcional y singularidad tipificación (la única alternativa que sé) para obtener más información.

Como usted dice, Monad es una estructura muy simple.La mitad de la respuesta es: Monad es la estructura más sencilla que podemos dar de lado a efectuar las funciones y ser capaz de utilizarlas.Con Monad podemos hacer dos cosas:podemos tratar un puro valor como efectos secundarios de valor (return), y se puede aplicar una efectos secundarios de la función para un efectos secundarios de valor para obtener una nueva con efectos secundarios de valor (>>=).Perder la capacidad de hacer cualquiera de estas cosas podría ser agobiante, así que nuestro efectos secundarios de tipo de necesidades a ser "al menos" Monad, y resulta que Monad es suficiente para poner en práctica todo lo que hemos necesitado hasta ahora.

La otra mitad es:¿cuál es la información más detallada de la estructura que podría darle a "posibles efectos secundarios"?Sin duda podemos pensar en el espacio de todos los posibles efectos secundarios como un conjunto (la única operación que se requiere es la membresía).Podemos combinar los dos efectos secundarios, haciendo de ellos uno tras otro, y esto va a dar lugar a diferentes efectos secundarios (o posiblemente el mismo - si la primera fue "apagar equipo" y la segunda "escribir archivo", luego el resultado de componer estos es sólo "apagar equipo").

Ok, entonces, ¿qué podemos decir acerca de esta operación?Es asociativa;es decir, si combinamos tres efectos secundarios, no importa en qué orden vamos a hacer la combinación en.Si vamos a hacer (escribir en el archivo, a continuación, leer socket), a continuación, apagar equipo, es lo mismo que hacer escribir en el archivo, a continuación, (leer socket, a continuación, apagar equipo).Pero no es conmutativa:("escribir archivo" y luego "eliminar archivo") es otro efecto secundario de ("borrar archivo", luego en "guardar archivo").Y tenemos una identidad:el especial efecto secundario "sin efectos secundarios" obras ("sin efectos secundarios", luego en "eliminar archivo" es el mismo efecto secundario simplemente como "eliminar archivo") En este punto, cualquier matemático es el pensamiento de Grupo"!" Pero los grupos tienen inversos, y no hay manera de invertir un efecto secundario en general;"eliminar archivo" es irreversible.Así, la estructura que nos queda es la de un monoid, lo que significa que nuestro lado-efectuar las funciones deben ser mónadas.

Hay una estructura más compleja?Seguro!Podríamos dividir a los posibles efectos secundarios en el sistema de ficheros basado en los efectos, de la red basado en efectos y más, y podemos llegar a más elaboradas reglas de composición que conservan estos detalles.Pero de nuevo se trata de: Monad es muy simple, y sin embargo lo suficientemente potente como para expresar la mayoría de las propiedades que nos interesa.(En particular, la asociatividad y los otros axiomas nos deja probar nuestra aplicación en trozos pequeños, con la confianza de que los efectos secundarios de la aplicación combinada será la misma que la combinación de los efectos secundarios de las piezas).

En realidad es una manera bastante limpio pensar en E / S de una manera funcional.

En la mayoría de los lenguajes de programación, se hacen operaciones de entrada / salida. En Haskell, imaginar la escritura de código no lo las operaciones, sino para generar una lista de las operaciones que le gustaría hacer.

Las mónadas son sintaxis sólo bonita para exactamente el mismo.

Si quieres saber por qué mónadas en contraposición a otra cosa, supongo que la respuesta es que son la mejor manera de representar funcional I / O que la gente podría pensar cuando estaban haciendo Haskell.

AFAIK, la razón es para ser capaz de incluir controles efectos secundarios en el sistema de tipos. Si quieres saber más, escuchar a los episodios SE-Radio : Episodio 108: Simon Peyton Jones en la programación funcional y Haskell Episodio 72: Erik Meijer en LINQ

Por encima hay muy buenas respuestas detalladas con antecedentes teóricos. Pero yo quiero dar mi opinión sobre IO mónada. No estoy experimentado programador Haskell, por lo que puede haber es bastante ingenuo o incluso erróneos. Pero yo me ayudó a lidiar con mónada IO, en cierta medida (nota, que NO se relaciona con otras mónadas).

En primer lugar quiero decir, que el ejemplo con el "mundo real" no es demasiado claro para mí ya que no podemos acceder a su (mundo real) estados anteriores. Puede ser que NO se relaciona con los cálculos monad en absoluto, sino que se desea en el sentido de la transparencia referencial, que es por lo general se presenta en el código Haskell.

Así que queremos que nuestro idioma (Haskell) a ser puro. Pero necesitamos las operaciones de entrada / salida ya que sin ellos nuestro programa no puede ser de utilidad. Y esas operaciones no pueden ser puros por su naturaleza. Así que la única manera de lidiar con esto tenemos que separar las operaciones impuros del resto del código.

Aquí viene mónada. En realidad, no estoy seguro, que no puede existir otra construcción con propiedades similares necesarios, pero el punto es que mónada tiene estas propiedades, por lo que se puede utilizar (y es utilizado con éxito). La principal propiedad es que no podemos escapar de ella. Interfaz mónada no tiene operaciones para deshacerse de la mónada alrededor de nuestro valor. Otros mónadas (no IO) proporcionan estas operaciones y permiten la coincidencia de patrones (por ejemplo, tal vez), pero esas operaciones no están en interfaz mónada. Otra propiedad requerida es la capacidad de operaciones de la cadena.

Si pensamos en lo que necesitamos en términos de sistema de tipos, llegamos al hecho de que tenemos que escribir con el constructor, que se puede envolver alrededor de cualquier valle. Constructor debe ser privada, como prohibimos escapar de él (es decir. La coincidencia de patrones). Pero necesitamos función para poner en valor este constructor (en este caso el retorno viene a la mente). Y necesitamos la forma de operaciones de la cadena. Si pensamos en ello durante algún tiempo, llegaremos al hecho, de que la operación de encadenamiento debe tener un tipo que tiene >> =. Así, llegamos a algo muy similar a la mónada. Creo que, si ahora analizar las posibles situaciones contradictorias con esta construcción, vamos a llegar a los axiomas mónada.

Tenga en cuenta, que se desarrolló constructo no tienen nada en común con la impureza. Sólo tiene propiedades, que deseaban tener que ser capaz de hacer frente a las operaciones impuras, es decir, sin escapatoria, encadenamiento, y una manera de entrar.

Ahora algún conjunto de operaciones impuros está predefinido por el idioma seleccionado dentro de esta mónada IO. Podemos combinar esas operaciones para crear nuevas operaciones unpure. Y todas esas operaciones tendrán que tener IO en su tipo. Tenga en cuenta sin embargo, que la presencia de IO en el tipo de alguna función no hacen esta función impura. Pero como yo lo entiendo, es mala idea para escribir funciones puras con IO en su tipo, ya que inicialmente era nuestra idea de separar las funciones puras e impuras.

Por último, quiero decir, que no se aparta mónada operaciones en puras. Sólo se permite separar de manera efectiva. (Repito, que es sólo mi entendimiento)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top