¿Por qué necesitamos mónadas?


366

En mi humilde opinión, las respuestas a la famosa pregunta "¿Qué es una mónada?" , especialmente los más votados, trate de explicar qué es una mónada sin explicar claramente por qué las mónadas son realmente necesarias . ¿Pueden explicarse como la solución a un problema?




44
¿Qué investigación has hecho ya? Donde has mirado ¿Qué recursos has encontrado? Esperamos que haga una gran cantidad de investigación antes de preguntar, y nos muestre en la pregunta qué investigación ha realizado . Hay muchos recursos que intentan explicar la motivación de los recursos: si no ha encontrado ninguno, es posible que deba investigar un poco más. Si has encontrado algunos pero no te ayudaron, sería una mejor pregunta si explicaras lo que encontraste y por qué específicamente no funcionaron para ti.
DW

8
Este es definitivamente un mejor ajuste para los programadores. StackExchange y no es un buen ajuste para StackOverflow. Votaría para migrar si pudiera, pero no puedo. = (
jpmc26

3
@ jpmc26 Lo más probable es que se cierre allí como "principalmente basado en opiniones"; aquí al menos tiene una oportunidad (como lo demuestra la gran cantidad de votos a favor, reapertura rápida ayer y no más votos cercanos todavía)
Izkata

Respuestas:


580

¿Por qué necesitamos mónadas?

  1. Queremos programar solo usando funciones . ("programación funcional (FP)" después de todo).
  2. Entonces, tenemos un primer gran problema. Este es un programa:

    f(x) = 2 * x

    g(x,y) = x / y

    ¿Cómo podemos decir qué se debe ejecutar primero ? ¿Cómo podemos formar una secuencia ordenada de funciones (es decir, un programa ) usando no más que funciones ?

    Solución: componer funciones . Si quieres primero gy luego f, solo escribe f(g(x,y)). De esta manera, "el programa" es una función, así: main = f(g(x,y)). Bien pero ...

  3. Más problemas: algunas funciones pueden fallar (es decir g(2,0), dividir por 0). No tenemos "excepciones" en FP (una excepción no es una función). ¿Como lo resolvemos?

    Solución: Permitamos que las funciones devuelvan dos tipos de cosas : en lugar de tener g : Real,Real -> Real(función de dos reales en real), permitamos g : Real,Real -> Real | Nothing(función de dos reales en (real o nada)).

  4. Pero las funciones deberían (para ser más simples) devolver solo una cosa .

    Solución: creemos un nuevo tipo de datos para devolver, un " tipo de boxeo " que encierra tal vez un real o simplemente nada. Por lo tanto, podemos tener g : Real,Real -> Maybe Real. Bien pero ...

  5. ¿Qué pasa ahora con f(g(x,y))? fno está listo para consumir a Maybe Real. Y no queremos cambiar todas las funciones con las que podríamos conectarnos gpara consumir a Maybe Real.

    Solución: tengamos una función especial para "conectar" / "componer" / "vincular" funciones . De esa manera, podemos, detrás de escena, adaptar la salida de una función para alimentar la siguiente.

    En nuestro caso: g >>= f(conectar / componer ga f). Queremos >>=obtener gel resultado, inspeccionarlo y, en caso de que sea Nothing, no llame fni regrese Nothing; o por el contrario, extraiga el cuadro Realy alimente fcon él. (Este algoritmo es solo la implementación de >>=para el Maybetipo). También tenga en cuenta que >>=debe escribirse solo una vez por "tipo de boxeo" (cuadro diferente, algoritmo de adaptación diferente).

  6. Surgen muchos otros problemas que pueden resolverse usando este mismo patrón: 1. Use una "caja" para codificar / almacenar diferentes significados / valores, y tener funciones como gesa devolver esos "valores en caja". 2. Tenga un compositor / enlazador g >>= fpara ayudar a conectar gla salida de fla entrada, de modo que no tengamos que cambiar nada f.

  7. Los problemas notables que se pueden resolver con esta técnica son:

    • teniendo un estado global que cada función en la secuencia de funciones ("el programa") puede compartir: solución StateMonad.

    • No nos gustan las "funciones impuras": funciones que producen diferentes resultados para la misma entrada. Por lo tanto, marquemos esas funciones, haciendo que devuelvan un valor etiquetado / encuadrado: IOmónada.

Felicidad total!


64
@Carl Por favor, escribe una mejor respuesta para iluminarnos
XrXr

15
@Carl Creo que está claro en la respuesta que hay muchos problemas que se benefician de este patrón (punto 6) y que la IOmónada es solo un problema más en la lista IO(punto 7). Por otro lado, IOsolo aparece una vez y al final, por lo tanto, no entiendo tu "la mayor parte del tiempo hablando ... sobre IO".
cibercitizen1

44
Los grandes conceptos erróneos sobre las mónadas: mónadas sobre el estado; mónadas sobre el manejo de excepciones; no hay forma de implementar IO en FPL puro sin mónadas; las mónadas son inequívocas (el contrargumento lo es Either). La mayor parte de la respuesta es sobre "¿Por qué necesitamos functors?".
vlastachu

44
"6. 2. Tenga un compositor / enlazador g >>= fpara ayudar a conectar gla salida a fla entrada, para que no tengamos que cambiar nada f". Esto no es correcto en absoluto. Antes, en f(g(x,y)), fpodía producir cualquier cosa. Podría ser f:: Real -> String. Con "composición monádica" se debe cambiar para producir Maybe String, o de lo contrario los tipos no encajarán. ¡Además, en >>=sí mismo no encaja ! Es lo >=>que hace esta composición, no >>=. Vea la discusión con dfeuer bajo la respuesta de Carl.
Will Ness

3
Su respuesta es correcta en el sentido de que las mónadas IMO se describen mejor como la composición / alidad de las "funciones" (las flechas de Kleisli realmente), pero los detalles precisos de qué tipo van a dónde son las que las hacen "mónadas". podría conectar las cajas de todo tipo de maneras (como Functor, etc.). Esta forma específica de conectarlos juntos es lo que define "la mónada".
Will Ness

219

La respuesta es, por supuesto, "No lo hacemos" . Como con todas las abstracciones, no es necesario.

Haskell no necesita una abstracción de mónada. No es necesario para realizar IO en un lenguaje puro. El IOtipo se encarga de eso por sí solo. El desugaring monádico existente de dobloques podría ser sustituido por desugaring a bindIO, returnIOy failIOcomo se define en el GHC.Basemódulo. (No es un módulo documentado sobre piratería, por lo que tendré que señalar su fuente de documentación). Entonces, no, no hay necesidad de la abstracción de mónada.

Entonces, si no es necesario, ¿por qué existe? Porque se descubrió que muchos patrones de cálculo forman estructuras monádicas. La abstracción de una estructura permite escribir código que funciona en todas las instancias de esa estructura. Para decirlo de manera más concisa: reutilización de código.

En lenguajes funcionales, la herramienta más poderosa encontrada para la reutilización de código ha sido la composición de funciones. El buen viejo (.) :: (b -> c) -> (a -> b) -> (a -> c)operador es extremadamente poderoso. Facilita la escritura de pequeñas funciones y las une con una mínima sobrecarga sintáctica o semántica.

Pero hay casos en que los tipos no funcionan del todo bien. ¿Qué haces cuando tienes foo :: (b -> Maybe c)y bar :: (a -> Maybe b)? foo . barno escribe, porque by Maybe bno son del mismo tipo.

Pero ... es casi correcto. Solo quieres un poco de libertad. Desea poder tratar Maybe bcomo si fuera básicamente b. Sin embargo, es una mala idea simplemente tratarlos como si fueran del mismo tipo. Eso es más o menos lo mismo que los punteros nulos, que Tony Hoare llamó el error de mil millones de dólares . Entonces, si no puede tratarlos como el mismo tipo, tal vez pueda encontrar una manera de extender el mecanismo de composición que (.)proporciona.

En ese caso, es importante examinar realmente la teoría subyacente (.). Afortunadamente, alguien ya ha hecho esto por nosotros. Resulta que la combinación de (.)y idforma una construcción matemática conocida como categoría . Pero hay otras formas de formar categorías. Una categoría de Kleisli, por ejemplo, permite que los objetos que se componen se aumenten un poco. Una categoría de Kleisli Maybeconsistiría en (.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c)y id :: a -> Maybe a. Es decir, los objetos en la categoría aumentan (->)con a Maybe, por lo que se (a -> b)convierte (a -> Maybe b).

Y de repente, hemos extendido el poder de la composición a cosas en las que la (.)operación tradicional no funciona. Esta es una fuente de nuevo poder de abstracción. Las categorías de Kleisli funcionan con más tipos que simplemente Maybe. Trabajan con todo tipo que pueda ensamblar una categoría adecuada, obedeciendo las leyes de categoría.

  1. Identidad izquierda: id . f=f
  2. Identidad correcta: f . id=f
  3. Asociatividad: f . (g . h)=(f . g) . h

Siempre que pueda probar que su tipo obedece esas tres leyes, puede convertirlo en una categoría Kleisli. ¿Y cuál es el problema con eso? Bueno, resulta que las mónadas son exactamente lo mismo que las categorías de Kleisli. Monad's returnes lo mismo que Kleisli id. Monad's (>>=)no es idéntico a Kleisli (.), pero resulta muy fácil escribir cada uno en términos del otro. Y las leyes de categoría son las mismas que las leyes de mónada, cuando las traduces a través de la diferencia entre (>>=)y (.).

Entonces, ¿por qué pasar por toda esta molestia? ¿Por qué tener una Monadabstracción en el lenguaje? Como mencioné anteriormente, permite la reutilización del código. Incluso permite la reutilización de código a lo largo de dos dimensiones diferentes.

La primera dimensión de la reutilización del código proviene directamente de la presencia de la abstracción. Puede escribir código que funcione en todas las instancias de la abstracción. Existe todo el paquete de mónada-bucles que consta de bucles que funcionan con cualquier instancia de Monad.

La segunda dimensión es indirecta, pero se sigue de la existencia de la composición. Cuando la composición es fácil, es natural escribir código en fragmentos pequeños y reutilizables. De la misma manera, tener el (.)operador para funciones fomenta la escritura de funciones pequeñas y reutilizables.

Entonces, ¿por qué existe la abstracción? Porque se ha demostrado que es una herramienta que permite una mayor composición en el código, lo que resulta en la creación de código reutilizable y fomenta la creación de más código reutilizable. La reutilización de código es uno de los santos griales de la programación. La abstracción de la mónada existe porque nos mueve un poco hacia ese santo grial.


2
¿Puede explicar la relación entre las categorías en general y las categorías de Kleisli? Las tres leyes que usted describe se mantienen en cualquier categoría.
dfeuer

1
@dfeuer Oh. Para ponerlo en código, newtype Kleisli m a b = Kleisli (a -> m b). Las categorías de Kleisli son funciones en las que el tipo de retorno categórico ( ben este caso) es el argumento para un constructor de tipos m. Iff Kleisli mforma una categoría, mes una mónada.
Carl

1
¿Qué es exactamente un tipo de retorno categórico? Kleisli mparece formar una categoría cuyos objetos son tipos de Haskell y tales que las flechas de aa bson las funciones de aa m b, con id = returny (.) = (<=<). ¿Es correcto, o estoy mezclando diferentes niveles de cosas o algo?
dfeuer

1
@dfeuer Eso es correcto. Los objetos son todos tipos, y los morfismos son entre tipos ay b, pero no son funciones simples. Están decoradas con un extra men el valor de retorno de la función.
Carl

1
¿Es realmente necesaria la terminología de la teoría de categorías? Tal vez, Haskell sería más fácil si convirtieras los tipos en imágenes donde el tipo sería el ADN de cómo se dibujan las imágenes (aunque los tipos dependientes son *), y luego usas la imagen para escribir tu programa con los nombres como pequeños caracteres rubí encima del icono
aoeu256

24

Benjamin Pierce dijo en TAPL

Se puede considerar que un sistema de tipos calcula un tipo de aproximación estática a los comportamientos de tiempo de ejecución de los términos en un programa.

Es por eso que un lenguaje equipado con un poderoso sistema de tipos es estrictamente más expresivo que un lenguaje mal escrito. Puedes pensar en las mónadas de la misma manera.

Como @Carl y sigfpe point, puede equipar un tipo de datos con todas las operaciones que desee sin recurrir a mónadas, clases de tipos o cualquier otra cosa abstracta. Sin embargo, las mónadas le permiten no solo escribir código reutilizable, sino también abstraer todos los detalles redundantes.

Como ejemplo, digamos que queremos filtrar una lista. La forma más simple es usar elfilter función:, filter (> 3) [1..10]que es igual [4,5,6,7,8,9,10].

Una versión un poco más complicada de filter, que también pasa un acumulador de izquierda a derecha, es

swap (x, y) = (y, x)
(.*) = (.) . (.)

filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]

Para obtener todo i, de modo que i <= 10, sum [1..i] > 4, sum [1..i] < 25podamos escribir

filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]

que es igual [3,4,5,6].

O podemos redefinir la nubfunción, que elimina elementos duplicados de una lista, en términos de filterAccum:

nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []

nub' [1,2,4,5,4,3,1,8,9,4]es igual [1,2,4,5,3,8,9]. Aquí se pasa una lista como acumulador. El código funciona, porque es posible salir de la mónada de la lista, por lo que todo el cómputo se mantiene puro (en realidad notElemno se usa >>=, pero podría). Sin embargo, no es posible abandonar con seguridad la mónada IO (es decir, no puede ejecutar una acción IO y devolver un valor puro; el valor siempre estará envuelto en la mónada IO). Otro ejemplo son las matrices mutables: después de haber dejado la mónada ST, donde vive una matriz mutable, ya no puede actualizar la matriz en tiempo constante. Entonces necesitamos un filtro monádico del Control.Monadmódulo:

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)

filterMejecuta una acción monádica para todos los elementos de una lista, produciendo elementos, para los cuales la acción monádica regresa True.

Un ejemplo de filtrado con una matriz:

nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs

main = print $ nub' [1,2,4,5,4,3,1,8,9,4]

imprime [1,2,4,5,3,8,9]como se esperaba.

Y una versión con la mónada IO, que pregunta qué elementos devolver:

main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn

P.ej

return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output

Y como ilustración final, filterAccumse puede definir en términos de filterM:

filterAccum f a xs = evalState (filterM (state . flip f) xs) a

con el StateT mónada, que se usa debajo del capó, siendo solo un tipo de datos ordinario.

Este ejemplo ilustra que las mónadas no solo le permiten abstraer el contexto computacional y escribir código reutilizable limpio (debido a la componibilidad de las mónadas, como explica @Carl), sino también tratar los tipos de datos definidos por el usuario y las primitivas integradas de manera uniforme.


1
Esta respuesta explica por qué necesitamos la clase de tipo Monad. La mejor manera de entender por qué necesitamos mónadas y no otra cosa es leer sobre la diferencia entre mónadas y functores aplicativos: uno , dos .
user3237465

20

No creo IOque deba verse como una mónada particularmente sobresaliente, pero ciertamente es una de las más asombrosas para principiantes, así que la usaré para mi explicación.

Construyendo ingenuamente un sistema IO para Haskell

El sistema IO más simple concebible para un lenguaje puramente funcional (y de hecho con el que Haskell comenzó) es este:

main :: String -> String
main _ = "Hello World"

Con pereza, esa firma simple es suficiente para construir programas de terminal interactivos , aunque muy limitada. Lo más frustrante es que solo podemos generar texto. ¿Qué pasa si agregamos algunas posibilidades de salida más interesantes?

data Output = TxtOutput String
            | Beep Frequency

main :: String -> [Output]
main _ = [ TxtOutput "Hello World"
          -- , Beep 440  -- for debugging
          ]

lindo, pero por supuesto una "salida alternativa" mucho más realista sería escribir en un archivo . Pero entonces también querrías alguna forma de leer archivos. ¿Cualquier oportunidad?

Bueno, cuando tomamos nuestro main₁programa y simplemente canalizamos un archivo al proceso (usando las instalaciones del sistema operativo), esencialmente hemos implementado la lectura de archivos. Si pudiéramos activar esa lectura de archivos desde el lenguaje Haskell ...

readFile :: Filepath -> (String -> [Output]) -> [Output]

Esto usaría un "programa interactivo" String->[Output] , lo alimentaría con una cadena obtenida de un archivo y generaría un programa no interactivo que simplemente ejecuta el dado.

Aquí hay un problema: realmente no tenemos una noción de cuándo se lee el archivo. La [Output]lista ciertamente da un buen orden a las salidas , pero no obtenemos un orden de cuándo se realizarán las entradas .

Solución: haga que los eventos de entrada también sean elementos en la lista de cosas que hacer.

data IO = TxtOut String
         | TxtIn (String -> [Output])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [Output])
         | Beep Double

main :: String -> [IO₀]
main _ = [ FileRead "/dev/null" $ \_ ->
             [TxtOutput "Hello World"]
          ]

Bien, ahora puede detectar un desequilibrio: puede leer un archivo y hacer que la salida dependa de él, pero no puede usar el contenido del archivo para decidir, por ejemplo, también leer otro archivo. Solución obvia: hacer que el resultado de los eventos de entrada también sea algo de tipo IO, no solo Output. Eso seguro incluye una salida de texto simple, pero también permite leer archivos adicionales, etc.

data IO = TxtOut String
         | TxtIn (String -> [IO₁])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [IO₁])
         | Beep Double

main :: String -> [IO₁]
main _ = [ TxtIn $ \_ ->
             [TxtOut "Hello World"]
          ]

Eso ahora le permitiría expresar cualquier operación de archivo que desee en un programa (aunque tal vez no con un buen rendimiento), pero es algo complicado:

  • main₃produce una lista completa de acciones. ¿Por qué no usamos simplemente la firma :: IO₁, que tiene esto como un caso especial?

  • Las listas ya no dan una descripción confiable del flujo del programa: la mayoría de los cálculos posteriores solo serán "anunciados" como resultado de alguna operación de entrada. Por lo tanto, podríamos deshacernos de la estructura de la lista, y simplemente contrarrestar un "y luego hacer" a cada operación de salida.

data IO = TxtOut String IO
         | TxtIn (String -> IO₂)
         | Terminate

main :: IO
main = TxtIn $ \_ ->
         TxtOut "Hello World"
          Terminate

¡No está mal!

Entonces, ¿qué tiene todo esto que ver con las mónadas?

En la práctica, no querrás usar constructores simples para definir todos tus programas. Tendría que haber un buen par de constructores tan fundamentales, pero para la mayoría de las cosas de nivel superior nos gustaría escribir una función con alguna buena firma de alto nivel. Resulta que la mayoría de estos se verían bastante similares: acepte algún tipo de valor con tipo significativo y produzca una acción IO como resultado.

getTime :: (UTCTime -> IO₂) -> IO
randomRIO :: Random r => (r,r) -> (r -> IO₂) -> IO
findFile :: RegEx -> (Maybe FilePath -> IO₂) -> IO

Evidentemente hay un patrón aquí, y será mejor que lo escribamos como

type IO a = (a -> IO₂) -> IO    -- If this reminds you of continuation-passing
                                  -- style, you're right.

getTime :: IO UTCTime
randomRIO :: Random r => (r,r) -> IO r
findFile :: RegEx -> IO (Maybe FilePath)

Ahora eso comienza a parecer familiar, pero todavía estamos lidiando con funciones simples poco disfrazadas bajo el capó, y eso es arriesgado: cada "acción de valor" tiene la responsabilidad de transmitir la acción resultante de cualquier función contenida (de lo contrario el flujo de control de todo el programa se ve fácilmente interrumpido por una acción de mal comportamiento en el medio). Será mejor que expliquemos ese requisito. Bueno, resulta que esas son las leyes de mónada , aunque no estoy seguro de que realmente podamos formularlas sin los operadores de enlace / unión estándar.

En cualquier caso, ahora hemos alcanzado una formulación de IO que tiene una instancia de mónada adecuada:

data IO a = TxtOut String (IO a)
           | TxtIn (String -> IO a)
           | TerminateWith a

txtOut :: String -> IO ()
txtOut s = TxtOut s $ TerminateWith ()

txtIn :: IO String
txtIn = TxtIn $ TerminateWith

instance Functor IO where
  fmap f (TerminateWith a) = TerminateWith $ f a
  fmap f (TxtIn g) = TxtIn $ fmap f . g
  fmap f (TxtOut s c) = TxtOut s $ fmap f c

instance Applicative IO where
  pure = TerminateWith
  (<*>) = ap

instance Monad IO where
  TerminateWith x >>= f = f x
  TxtOut s c >>= f = TxtOut s $ c >>= f
  TxtIn g >>= f = TxtIn $ (>>=f) . g

Obviamente, esta no es una implementación eficiente de IO, pero en principio es utilizable.


@jdlugosz: IO3 a ≡ Cont IO2 a. Pero quise decir ese comentario más como un guiño a aquellos que ya conocen la continuación de la mónada, ya que no tiene exactamente la reputación de ser amigable para principiantes.
Leftaroundabout

4

Las mónadas son solo un marco conveniente para resolver una clase de problemas recurrentes. Primero, las mónadas deben ser functores (es decir, deben admitir la asignación sin mirar los elementos (o su tipo)), también deben traer una operación de enlace (o encadenamiento) y una forma de crear un valor monádico a partir de un tipo de elemento ( return). Finalmente, bindy returndebe satisfacer dos ecuaciones (identidades izquierda y derecha), también llamadas leyes de mónada. (Alternativamente, uno podría definir mónadas para tener unflattening operation lugar de vinculante).

La lista mónada se usa comúnmente para tratar el no determinismo. La operación de vinculación selecciona un elemento de la lista (intuitivamente todos en mundos paralelos ), le permite al programador hacer algunos cálculos con ellos y luego combina los resultados en todos los mundos en una sola lista (concatenando o aplanar una lista anidada) ) Así es como se definiría una función de permutación en el marco monádico de Haskell:

perm [e] = [[e]]
perm l = do (leader, index) <- zip l [0 :: Int ..]
            let shortened = take index l ++ drop (index + 1) l
            trailer <- perm shortened
            return (leader : trailer)

Este es un ejemplo repl sesión:

*Main> perm "a"
["a"]
*Main> perm "ab"
["ab","ba"]
*Main> perm ""
[]
*Main> perm "abc"
["abc","acb","bac","bca","cab","cba"]

Cabe señalar que la mónada de la lista no es de ninguna manera un cómputo de efectos secundarios. Una estructura matemática que es una mónada (es decir, conforme a las interfaces y leyes mencionadas anteriormente) no implica efectos secundarios, aunque los fenómenos de efectos secundarios a menudo encajan muy bien en el marco monádico.


3

Las mónadas sirven básicamente para componer funciones juntas en una cadena. Período.

Ahora, la forma en que se componen difiere entre las mónadas existentes, lo que resulta en diferentes comportamientos (por ejemplo, para simular un estado mutable en la mónada estatal).

La confusión acerca de las mónadas es que, al ser tan generales, es decir, un mecanismo para componer funciones, se pueden usar para muchas cosas, lo que lleva a las personas a creer que las mónadas son sobre el estado, sobre IO, etc., cuando solo se trata de "componer funciones ".

Ahora, una cosa interesante sobre las mónadas, es que el resultado de la composición siempre es del tipo "M a", es decir, un valor dentro de un sobre etiquetado con "M". Esta característica resulta realmente agradable de implementar, por ejemplo, una separación clara entre el código puro del impuro: declare todas las acciones impuras como funciones de tipo "IO a" y no proporcione ninguna función, al definir la mónada IO, para eliminar el " un "valor desde dentro del" IO a ". El resultado es que ninguna función puede ser pura y al mismo tiempo extraer un valor de un "IO a", porque no hay forma de tomar dicho valor mientras se mantiene puro (la función debe estar dentro de la mónada "IO" para usar tal valor). (NOTA: bueno, nada es perfecto, por lo que la "camisa de fuerza IO" se puede romper usando "unsafePerformIO: IO a -> a"


2

Necesita mónadas si tiene un constructor de tipos y funciones que devuelven valores de esa familia de tipos . Eventualmente, le gustaría combinar este tipo de funciones juntas . Estos son los tres elementos clave para responder por qué .

Déjame elaborar. Tienes Int, Stringy Realfunciones de tipo Int -> String, String -> Realy así sucesivamente. Puede combinar estas funciones fácilmente, terminando conInt -> Real . La vida es buena.

Entonces, un día, necesitas crear una nueva familia de tipos . Podría deberse a que debe considerar la posibilidad de no devolver ningún valor ( Maybe), devolver un error ( Either), múltiples resultados ( List), etc.

Tenga en cuenta que Maybees un constructor de tipos. Toma un tipo, me gusta Inty devuelve un nuevo tipo Maybe Int. Lo primero que debe recordar, sin constructor de tipo, sin mónada.

Por supuesto, desea utilizar su constructor de tipos en su código, y pronto terminará con funciones como Int -> Maybe Stringy String -> Maybe Float. Ahora, no puede combinar fácilmente sus funciones. La vida ya no es buena.

Y aquí es cuando las mónadas vienen al rescate. Le permiten combinar ese tipo de funciones nuevamente. Solo necesitas cambiar la composición . para > == .


2
Esto no tiene nada que ver con las familias de tipos. ¿De qué estás hablando realmente?
dfeuer
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.