2009-10-25 25 views
11

Estoy aprendiendo acerca de las mónadas y tengo algunas preguntas.Algunas preguntas sobre las mónadas en Haskell

Aquí es donde estoy ahora. Por favor, corrígeme donde estoy equivocado.

  • El símbolo >>= es un operador infijo. Los operadores Infix son funciones que toman dos argumentos (lado izquierdo y lado derecho) y devuelven un valor.

  • El símbolo >>= se denomina operador de vinculación y tiene la firma Monad m => m t -> (t -> m u) -> m u. Sin embargo, los tipos no parecen alinearse aquí. Obtenemos un valor de tipo m t y el segundo argumento es una función que toma un t. (No veo cómo conectar los puntos.)

  • Esto debe significar que la función se unen de alguna manera es capaz de eliminar el m del m t con el fin de obtener la t y pasarlo a la función.

Aquí están mis preguntas:

  • es la capacidad de eliminar el m algo de m t que sólo es posible dentro de un operador, tal aprieto. ¿Este operador de enlace tiene algunos privilegios especiales o algo así?

  • ¿Qué tiene que ver con los cambios de estado? Entiendo (creo) que el objetivo de las mónadas es "envolver" los efectos secundarios para que estén aislados del resto del programa. Pero, ¿cuál es el papel del operador de enlace en esto?

Respuesta

10

Es la capacidad de eliminar la 'M' de 'M t' algo que solo es posible dentro de dicho operador de vinculación. ¿Este operador de enlace tiene algunos privilegios especiales o algo así?

Enlazar no es de ninguna manera un caso especial, pero generalmente se definirá en el mismo módulo que el tipo de datos de mónadas. Por lo tanto, podría conocer (y usar) detalles que el módulo no exporta. El caso habitual sería que el módulo exporte un tipo de datos, pero no sus constructores u otros detalles sobre la estructura interna de los tipos. Luego, para el código que usa el módulo, el funcionamiento interno del tipo de datos es invisible y ese código no puede modificar directamente los valores de este tipo.

que se oponen a que las funciones definidas en el interior del módulo, como por ejemplo algún operador se unen >>=, puede acceder a lo que quieran desde el módulo que se definen en. Así que estas funciones podrían ser capaces de hacer las cosas funciones "externos" no pueden hacer.

Un caso especial es la mónada IO, ya que no está definida por un módulo, sino que está integrada en el sistema de ejecución/compilador. Aquí el compilador conoce los detalles internos de su implementación y expone funciones como IO 's >>=. Las implementaciones de estas funciones son especialmente privilegiadas ya que viven "fuera del programa", pero este es un caso especial y este hecho no debería ser observable desde dentro de Haskell.

¿Qué tiene que ver con los cambios de estado? Entiendo (creo) que el objetivo de las mónadas es "envolver" los efectos secundarios para que estén aislados del resto del programa. Pero, ¿cuál es el papel del operador de enlace en esto?

Realmente no necesita tener que ver con los cambios de estado, este es solo un problema que se puede manejar con moands. La mónada IO se usa para ejecutar IO en un orden determinado, pero generalmente las mónadas son solo formas de combinar funciones juntas.

Generalmente una mónada (específicamente su función de vinculación) define una forma en que ciertas funciones se deben componer juntas para funciones más grandes. Este método de combinar funciones se abstrae en la mónada. No es importante cómo funciona exactamente esta combinación o por qué querría combinar funciones de esta manera, una mónada simplemente especifica una manera de combinar ciertas funciones de una determinada manera. (Ver también this "Monads for C# programmers" answer donde básicamente lo repito unas cuantas veces con ejemplos.)

+1

Esta respuesta tiene algunas afirmaciones potencialmente falsas sobre 'IO'. En GHC, al menos, la instancia 'Monad' para' IO' * se * define en un módulo, a saber 'GHC.Base', usando detalles internos" semiprivados "del tipo' IO'. Por supuesto, otras implementaciones son libres de hacer algo diferente. – dfeuer

12

es la capacidad de eliminar la 'M' desde 'M t' algo que sólo es posible dentro de un operador, tal aprieto.

Bueno, ciertamente es posible dentro del operador se unen, como su tipo especifica:

(>>=) :: m a -> (a -> m b) -> m b 

La función de 'correr' para su mónada por lo general puede hacer esto también (para regresar un valor puro a partir de tu computación).

el objetivo de mónadas es el de 'envolver' efectos secundarios de modo que están aislados del resto del programa

Hmm. No, las mónadas nos permiten modelar nociones de computación. Los cálculos de efecto lateral son solo una de esas nociones, como estado, retroceso, continuación, concurrencia, transacciones, resultados opcionales, resultados aleatorios, estado revertable, no determinismo ... todos los cuales can be described as a monad

IO mónada es lo te refieres, supongo. Es una mónada un tanto extraña: genera secuencias de cambios abstractos en el estado mundial, que luego son evaluados por el tiempo de ejecución. Bind solo nos permite secuenciar las cosas en el orden correcto en la mónada IO, y el compilador traducirá todas estas acciones secuenciadas de modificación del mundo en un código imperativo que cambia ese estado de la máquina.

Eso es muy específico para la mónada IO, no mónadas en general.

+0

¿Cómo obtiene la carpeta la capacidad de eliminar la 'M' de la 'M t'? ¿Es este un privilegio especial que obtienen las funciones de enlace? – StackedCrooked

+2

Suponiendo que el tipo monádico no está oculto, cualquier función puede separar la M, sin embargo, la notación do ocultará la mayoría de las tuberías, lo que significa que '>> =' logrará deconstruir el estado interno. Tener solo funciones bind y runM deconstruir el estado es solo una expresión idiomática, no es obligatorio. –

5

La siguiente es la definición de la clase de tipo Monad.

class Monad m where 

    (>>=)  :: forall a b. m a -> (a -> m b) -> m b 
    (>>)  :: forall a b. m a -> m b -> m b 
    return  :: a -> m a 
    fail  :: String -> m a 

    m >> k  = m >>= \_ -> k 
    fail s  = error s 

Cada tipo-instancia de tipo de clase Monad define su propia función >>=.Este es un ejemplo del tipo de instancia Maybe:

instance Monad Maybe where 

    (Just x) >>= k  = k x 
    Nothing >>= _  = Nothing 

    (Just _) >> k  = k 
    Nothing >> _  = Nothing 

    return    = Just 
    fail _    = Nothing 

Como podemos ver, porque la versión Maybe de >>= está especialmente definido para entender la Maybe tipo de instancia, y porque se define en un lugar que tiene acceso legal a los data Maybe a constructores de datos Nothing y Just a, la versión Maybe de >>= es capaz de desenvolver el a 's en Maybe a y pasarlos.

trabajar a través de un ejemplo, podríamos tener:

x :: Maybe Integer 
x = do a <- Just 5 
     b <- Just (a + 1) 
     return b 

De-azucarada, el do-notación se convierte en:

x :: Maybe Integer 
x = Just 5  >>= \a -> 
    Just (a + 1) >>= \b -> 
    Just b 

que evalúa como:

=     (\a -> 
    Just (a + 1) >>= \b -> 
    Just b) 5 

    = Just (5 + 1) >>= \b -> 
    Just b 

    =     (\b -> 
    Just b) (5 + 1) 

    = Just (5 + 1) 

    = Just 6 
4

El los tipos se alinean, curiosamente. Así es cómo.

Recuerde que una mónada también es un functor. La siguiente función se define para todos los functores:

fmap :: (Functor f) => (a -> b) -> f a -> f b 

Ahora la pregunta: ¿estos tipos realmente se alinean? Bueno, sí. Dada una función de a a b, entonces si tenemos un entorno f en el que a está disponible, tenemos un entorno f en el que b está disponible.

Por analogía con el silogismo:

(Functor Socrates) => (Man -> Mortal) -> Socrates Man -> Socrates Mortal 

Ahora bien, como es sabido, una mónada es un funtor equipadas con bind y regreso:

return :: (Monad m) => a -> m a 
(=<<) :: (Monad m) => (a -> m b) -> m a -> m b 

puede que no sepa que es equivalente, que es un funtor equipadas con retorno y se unen:

join :: (Monad m) => m (m a) -> m a 

Vea cómo estamos pelando una m. Con una mónada m, no siempre puede obtener desde m a hasta a, pero siempre puede obtener desde m (m a) hasta m a.

Ahora mira el primer argumento para . Es una función del tipo (a -> m b). ¿Qué sucede cuando pasa esa función al fmap? Obtienes m a -> m (m b). Por lo tanto, "mapeo" en un m a con una función a -> m b le da m (m b). Tenga en cuenta que esto es exactamente como el tipo de argumento para join. Esto no es una coincidencia.Una aplicación razonable de "unir" se parece a esto:

(>>=) :: m a -> (a -> m b) -> m b 
x >>= f = join (fmap f x) 

De hecho, se unen y unirse se puede definir en términos de uno al otro:

join = (>>= id) 
2

entiendo (creo) que el El objetivo de las mónadas es "envolver" los efectos secundarios para que estén aislados del resto del programa.

Es en realidad un poco más sutil que eso. Las mónadas nos permiten modelar la secuencia de una manera muy general. A menudo, cuando hablas con un experto en dominios, los encuentras diciendo algo así como "primero probamos X. Entonces probamos Y, y si eso no funciona, probamos Z". Cuando llega a implementar algo así en un lenguaje convencional, encuentra que no encaja, por lo que tiene que escribir muchos códigos adicionales para cubrir lo que el experto del dominio quiere decir con la palabra "entonces".

En Haskell puede implementar esto como una mónada con el "entonces" traducido en el operador de vinculación. Así que, por ejemplo, una vez escribí un programa en el que se tenía que asignar un elemento desde los pools de acuerdo con ciertas reglas. Para el caso 1 lo tomaste del grupo X. Si eso estaba vacío, pasaste al grupo Y. Para el caso 2, tenías que tomarlo directamente del grupo Y. Y así sucesivamente, para una docena de casos, incluyendo algunos donde tomaste el menos usado recientemente en la piscina, ya sea X o Y. me escribió una mónada personalizada especialmente para ese trabajo para que yo pudiera escribir:

case c of 
    1: do {try poolX; try poolY} 
    2: try poolY 
    3: try $ lru [poolX, poolY] 

funcionó muy bien.

Por supuesto, esto incluye modelos convencionales de secuenciación. La mónada IO es el modelo que tienen todos los demás lenguajes de programación; es solo que en Haskell es una elección explícita más que parte del medio ambiente. La mónada ST le da la mutación de memoria de IO, pero sin la entrada y salida real. Por otro lado, la mónada de estado le permite restringir su estado a un único valor de un tipo con nombre.

Para algo realmente inclinado, vea this blog post sobre una mónada de estado hacia atrás. El estado se propaga en la dirección opuesta a la "ejecución". Si piensas en esto como una mónada de estado que ejecuta una instrucción seguida de la siguiente, entonces un "put" enviará el valor de estado hacia atrás en el tiempo a cualquier "get" anterior. Lo que sucede realmente es que se configura una función mutuamente recursiva que solo finaliza si no hay paradojas. No estoy seguro de cómo usar tal mónada, pero ilustra el punto acerca de que las mónadas son modelos de computación.

Si no está listo para eso, piense en bind como un punto y coma que se puede descargar. Eso te lleva bastante lejos.

Cuestiones relacionadas