haskell - que - ¿Qué es una mónada?
monada maybe (30)
tl; dr
{-# LANGUAGE InstanceSigs #-}
newtype Id t = Id t
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
Prólogo
El operador $
de la aplicación de funciones
forall a b. a -> b
se define canónicamente
($) :: (a -> b) -> a -> b
f $ x = f x
infixr 0 $
en términos de aplicación de función primitiva Haskell fx
( infixl 10
).
La composición .
se define en términos de $
como
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = / x -> f $ g x
infixr 9 .
y satisface las equivalencias forall fg h.
f . id = f :: c -> d Right identity
id . g = g :: b -> c Left identity
(f . g) . h = f . (g . h) :: a -> d Associativity
.
es asociativo, y id
es su identidad derecha e izquierda.
El triple de Kleisli
En programación, una mónada es un constructor de tipo functor con una instancia de la clase de tipo mónada. Hay varias variantes equivalentes de definición e implementación, cada una con intuiciones ligeramente diferentes sobre la abstracción de la mónada.
Un functor es un constructor f
de tipo de tipo * -> *
con una instancia de la clase de tipo functor.
{-# LANGUAGE KindSignatures #-}
class Functor (f :: * -> *) where
map :: (a -> b) -> (f a -> f b)
Además de seguir el protocolo de tipo forzado estáticamente, las instancias de la clase de tipo functor deben obedecer las leyes de funge algebraicas forall f g.
map id = id :: f t -> f t Identity
map f . map g = map (f . g) :: f a -> f c Composition / short cut fusion
Los cálculos de functor tienen el tipo
forall f t. Functor f => f t
Un cálculo cr
consiste en resultados r
dentro del contexto c
.
Las funciones monádicas unarias o las flechas de Kleisli tienen el tipo
forall m a b. Functor m => a -> m b
Las flechas de Kleisi son funciones que toman un argumento a
y devuelven un cálculo monádico mb
.
Las mónadas se definen canónicamente en términos del triple de Kleisli forall m. Functor m =>
(m, return, (=<<))
implementado como la clase de tipo
class Functor m => Monad m where
return :: t -> m t
(=<<) :: (a -> m b) -> m a -> m b
infixr 1 =<<
La identidad de Kleisli return
es una flecha de Kleisli que promueve un valor t
en un contexto monádico m
. La aplicación de extensión o Kleisli=<<
aplica una flecha de Kleisli a -> mb
a los resultados de un cálculo ma
.
La composición de Kleisli <=<
se define en términos de extensión como
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = / x -> f =<< g x
infixr 1 <=<
<=<
compone dos flechas Kleisli, aplicando la flecha izquierda a los resultados de la aplicación de la flecha derecha.
Las instancias de la clase de tipo mónada deben obedecer las leyes de mónada , más elegantemente expresadas en términos de composición de Kleisli:forall fg h.
f <=< return = f :: c -> m d Right identity
return <=< g = g :: b -> m c Left identity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d Associativity
<=<
es asociativo, y return
es su identidad derecha e izquierda.
Identidad
El tipo de identidad
type Id t = t
es la función de identidad en tipos
Id :: * -> *
Interpretado como un functor,
return :: t -> Id t
= id :: t -> t
(=<<) :: (a -> Id b) -> Id a -> Id b
= ($) :: (a -> b) -> a -> b
(<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
= (.) :: (b -> c) -> (a -> b) -> (a -> c)
En Haskell canónico, se define la mónada de identidad
newtype Id t = Id t
instance Functor Id where
map :: (a -> b) -> Id a -> Id b
map f (Id x) = Id (f x)
instance Monad Id where
return :: t -> Id t
return = Id
(=<<) :: (a -> Id b) -> Id a -> Id b
f =<< (Id x) = f x
Opción
Un tipo de opción
data Maybe t = Nothing | Just t
codifica la computación Maybe t
que no necesariamente produce un resultado t
, computación que puede "fallar". La opción mónada está definida
instance Functor Maybe where
map :: (a -> b) -> (Maybe a -> Maybe b)
map f (Just x) = Just (f x)
map _ Nothing = Nothing
instance Monad Maybe where
return :: t -> Maybe t
return = Just
(=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
f =<< (Just x) = f x
_ =<< Nothing = Nothing
a -> Maybe b
se aplica a un resultado solo si Maybe a
produce un resultado.
newtype Nat = Nat Int
Los números naturales pueden codificarse como aquellos enteros mayores o iguales a cero.
toNat :: Int -> Maybe Nat
toNat i | i >= 0 = Just (Nat i)
| otherwise = Nothing
Los números naturales no se cierran bajo resta.
(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)
infixl 6 -?
La opción mónada cubre una forma básica de manejo de excepciones.
(-? 20) <=< toNat :: Int -> Maybe Nat
Lista
La mónada de la lista, sobre el tipo de lista
data [] t = [] | t : [t]
infixr 5 :
y su operación monoide aditiva "agregar"
(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[] ++ ys = ys
infixr 5 ++
codifica el cálculo no lineal que[t]
produce una cantidad natural 0, 1, ...
de resultados t
.
instance Functor [] where
map :: (a -> b) -> ([a] -> [b])
map f (x : xs) = f x : map f xs
map _ [] = []
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> [a] -> [b]
f =<< (x : xs) = f x ++ (f =<< xs)
_ =<< [] = []
La extensión =<<
concatena ++
todas las listas [b]
resultantes de las aplicaciones fx
de una flecha de Kleisli a -> [b]
a elementos de [a]
una sola lista de resultados [b]
.
Deje que los divisores propios de un entero positivo n
sean
divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]
divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)
luego
forall n. let { f = f <=< divisors } in f n = []
Al definir la clase de tipo mónada, en lugar de extensión =<<
, el estándar Haskell usa su operador flip, el enlace>>=
.
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= / _ -> k
{-# INLINE (>>) #-}
return :: a -> m a
return = pure
Por simplicidad, esta explicación usa la jerarquía de clases de tipos
class Functor f
class Functor m => Monad m
En Haskell, la jerarquía estándar actual es
class Functor f
class Functor p => Applicative p
class Applicative m => Monad m
porque no solo cada mónada es functor, sino que cada aplicativo es un ficticio y cada mónada también es un aplicativo.
Usando la lista mónada, el pseudocódigo imperativo
for a in (1, ..., 10)
for b in (1, ..., 10)
p <- a * b
if even(p)
yield p
se traduce aproximadamente al bloque do ,
do a <- [1 .. 10]
b <- [1 .. 10]
let p = a * b
guard (even p)
return p
la comprensión de mónada equivalente ,
[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]
y la expresión
[1 .. 10] >>= (/ a ->
[1 .. 10] >>= (/ b ->
let p = a * b in
guard (even p) >> -- [ () | even p ] >>
return p
)
)
La comprensión de notación y mónada son azúcar sintáctica para expresiones de enlace anidadas. El operador de enlace se usa para el enlace de nombre local de resultados monádicos.
let x = v in e = (/ x -> e) $ v = v & (/ x -> e)
do { r <- m; c } = (/ r -> c) =<< m = m >>= (/ r -> c)
dónde
(&) :: a -> (a -> b) -> b
(&) = flip ($)
infixl 0 &
La función de guardia está definida
guard :: Additive m => Bool -> m ()
guard True = return ()
guard False = fail
donde el tipo de unidad o "tupla vacía"
data () = ()
Las mónadas aditivas que admiten elección y falla pueden abstraerse utilizando una clase de tipo
class Monad m => Additive m where
fail :: m t
(<|>) :: m t -> m t -> m t
infixl 3 <|>
instance Additive Maybe where
fail = Nothing
Nothing <|> m = m
m <|> _ = m
instance Additive [] where
fail = []
(<|>) = (++)
donde fail
y <|>
formar un monoideforall kl m.
k <|> fail = k
fail <|> l = l
(k <|> l) <|> m = k <|> (l <|> m)
y fail
es el elemento cero absorbente / aniquilador de mónadas aditivas
_ =<< fail = fail
Si en
guard (even p) >> return p
even p
es cierto, entonces el guardia produce [()]
y, por definición de >>
, la función constante local
/ _ -> return p
se aplica al resultado ()
. Si es falso, el guardia produce la lista mónada fail
( []
), que no produce ningún resultado para que se aplique una flecha de Kleisli >>
, por lo que p
se omite.
Estado
Infamemente, las mónadas se utilizan para codificar la computación con estado.
Un procesador de estado es una función.
forall st t. st -> (t, st)
que transiciona un estado st
y produce un resultado t
. El estado st
puede ser cualquier cosa. Nada, bandera, conteo, matriz, mango, máquina, mundo.
El tipo de procesadores de estado generalmente se llama
type State st t = st -> (t, st)
La mónada del procesador de estado es el * -> *
functor amable State st
. Las flechas de Kleisli de la mónada del procesador de estado son funciones
forall st a b. a -> (State st) b
En Haskell canónico, se define la versión perezosa de la mónada del procesador de estado
newtype State st t = State { stateProc :: st -> (t, st) }
instance Functor (State st) where
map :: (a -> b) -> ((State st) a -> (State st) b)
map f (State p) = State $ / s0 -> let (x, s1) = p s0
in (f x, s1)
instance Monad (State st) where
return :: t -> (State st) t
return x = State $ / s -> (x, s)
(=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
f =<< (State p) = State $ / s0 -> let (x, s1) = p s0
in stateProc (f x) s1
Un procesador de estado se ejecuta proporcionando un estado inicial:
run :: State st t -> st -> (t, st)
run = stateProc
eval :: State st t -> st -> t
eval = fst . run
exec :: State st t -> st -> st
exec = snd . run
El acceso estatal es proporcionado por primitivas get
y put
métodos de abstracción sobre mónadas con estado :
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
declara una dependencia funcional del tipo de estado st
en la mónada m
; que a State t
, por ejemplo, determinará que el tipo de estado sea t
exclusivo.
instance Stateful (State st) st where
get :: State st st
get = State $ / s -> (s, s)
put :: st -> State st ()
put s = State $ / _ -> ((), s)
con el tipo de unidad utilizado de forma análoga a void
en C.
modify :: Stateful m st => (st -> st) -> m ()
modify f = do
s <- get
put (f s)
gets :: Stateful m st => (st -> t) -> m t
gets f = do
s <- get
return (f s)
gets
a menudo se usa con los accesos de campo de registro.
El estado de la mónada equivalente del subproceso variable
let s0 = 34
s1 = (+ 1) s0
n = (* 12) s1
s2 = (+ 7) s1
in (show n, s2)
donde s0 :: Int
, es igualmente transparente referencialmente, pero infinitamente más elegante y práctico
(flip run) 34
(do
modify (+ 1)
n <- gets (* 12)
modify (+ 7)
return (show n)
)
modify (+ 1)
es un cálculo de tipo State Int ()
, excepto por su efecto equivalente a return ()
.
(flip run) 34
(modify (+ 1) >>
gets (* 12) >>= (/ n ->
modify (+ 7) >>
return (show n)
)
)
La ley de asociatividad de la mónada se puede escribir en términos de >>=
forall mf g.
(m >>= f) >>= g = m >>= (/ x -> f x >>= g)
o
do { do { do {
r1 <- do { x <- m; r0 <- m;
r0 <- m; = do { = r1 <- f r0;
f r0 r1 <- f x; g r1
}; g r1 }
g r1 }
} }
Al igual que en la programación orientada a la expresión (por ejemplo, Rust), la última declaración de un bloque representa su rendimiento. El operador de enlace a veces se denomina "punto y coma programable".
Las primitivas de la estructura de control de iteración de la programación imperativa estructurada se emulan monádicamente.
for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())
while :: Monad m => m Bool -> m t -> m ()
while c m = do
b <- c
if b then m >> while c m
else return ()
forever :: Monad m => m t
forever m = m >> forever m
De entrada y salida
data World
La mónada del procesador de estado mundial de E / S es una reconciliación de Haskell puro y el mundo real, de la semántica operativa imperativa y denotativa funcional. Un análogo cercano de la implementación estricta real:
type IO t = World -> (t, World)
La interacción es facilitada por primitivas impuras
getChar :: IO Char
putChar :: Char -> IO ()
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()
hSetBuffering :: Handle -> BufferMode -> IO ()
hTell :: Handle -> IO Integer
. . . . . .
La impureza del código que usa IO
primitivas está permanentemente protocolizada por el sistema de tipos. Debido a que la pureza es asombrosa, lo que sucede en IO
, se queda adentro IO
.
unsafePerformIO :: IO t -> t
O, al menos, debería.
La firma tipo de un programa Haskell
main :: IO ()
main = putStrLn "Hello, World!"
se expande a
World -> ((), World)
Una función que transforma un mundo.
Epílogo
La categoría cuyos objetos son tipos de Haskell y cuyos morfismos son funciones entre los tipos de Haskell es, "rápido y suelto", la categoría Hask
.
Un functor T
es un mapeo de una categoría C
a una categoría D
; para cada objeto en C
un objeto enD
Tobj : Obj(C) -> Obj(D)
f :: * -> *
y para cada morfismo en C
un morfismo enD
Tmor : HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
map :: (a -> b) -> (f a -> f b)
donde X
, Y
son objetos en C
. HomC(X, Y)
es la clase de homomorfismo de todos los morfismos X -> Y
en C
. El functor debe preservar la identidad y composición del morfismo, la "estructura" de C
, en D
.
Tmor Tobj
T(id) = id : T(X) -> T(X) Identity
T(f) . T(g) = T(f . g) : T(X) -> T(Z) Composition
La categoría Kleisli de una categoría C
está dada por un triple Kleisli
<T, eta, _*>
de un endofunctor
T : C -> C
( f
), un morfismo de identidad eta
( return
) y un operador de extensión *
( =<<
).
Cada morfismo de Kleisli en Hask
f : X -> T(Y)
f :: a -> m b
por el operador de extensión
(_)* : Hom(X, T(Y)) -> Hom(T(X), T(Y))
(=<<) :: (a -> m b) -> (m a -> m b)
se le da un morfismo en Hask
la categoría de Kleisli
f* : T(X) -> T(Y)
(f =<<) :: m a -> m b
La composición en la categoría Kleisli .T
se da en términos de extensión
f .T g = f* . g : X -> T(Z)
f <=< g = (f =<<) . g :: a -> m c
y satisface los axiomas de categoría
eta .T g = g : Y -> T(Z) Left identity
return <=< g = g :: b -> m c
f .T eta = f : Z -> T(U) Right identity
f <=< return = f :: c -> m d
(f .T g) .T h = f .T (g .T h) : X -> T(U) Associativity
(f <=< g) <=< h = f <=< (g <=< h) :: a -> m d
que, aplicando las transformaciones de equivalencia
eta .T g = g
eta* . g = g By definition of .T
eta* . g = id . g forall f. id . f = f
eta* = id forall f g h. f . h = g . h ==> f = g
(f .T g) .T h = f .T (g .T h)
(f* . g)* . h = f* . (g* . h) By definition of .T
(f* . g)* . h = f* . g* . h . is associative
(f* . g)* = f* . g* forall f g h. f . h = g . h ==> f = g
en términos de extensión se dan canónicamente
eta* = id : T(X) -> T(X) Left identity
(return =<<) = id :: m t -> m t
f* . eta = f : Z -> T(U) Right identity
(f =<<) . return = f :: c -> m d
(f* . g)* = f* . g* : T(X) -> T(Z) Associativity
(((f =<<) . g) =<<) = (f =<<) . (g =<<) :: m a -> m c
Las mónadas también se pueden definir en términos no de extensión de Kleislian, sino de una transformación natural mu
, en la programación llamada join
. Una mónada se define en términos de mu
un triple sobre una categoría C
, de un endofunctor
T : C -> C
f :: * -> *
y dos transformaciones naturales
eta : Id -> T
return :: t -> f t
mu : T . T -> T
join :: f (f t) -> f t
satisfaciendo las equivalencias
mu . T(mu) = mu . mu : T . T . T -> T . T Associativity
join . map join = join . join :: f (f (f t)) -> f t
mu . T(eta) = mu . eta = id : T -> T Identity
join . map return = join . return = id :: f t -> f t
La clase de tipo mónada se define entonces
class Functor m => Monad m where
return :: t -> m t
join :: m (m t) -> m t
La mu
implementación canónica de la opción mónada:
instance Monad Maybe where
return = Just
join (Just m) = m
join Nothing = Nothing
La concat
función
concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat [] = []
es el join
de la lista mónada.
instance Monad [] where
return :: t -> [t]
return = (: [])
(=<<) :: (a -> [b]) -> ([a] -> [b])
(f =<<) = concat . map f
Las implementaciones de join
se pueden traducir del formulario de extensión utilizando la equivalencia
mu = id* : T . T -> T
join = (id =<<) :: m (m t) -> m t
La traducción inversa de mu
a forma de extensión viene dada por
f* = mu . T(f) : T(X) -> T(Y)
(f =<<) = join . map f :: m a -> m b
Philip Wadler: mónadas para la programación funcional
Simon L Peyton Jones, Philip Wadler: programación funcional imperativa
Jonathan MD Hill, Keith Clarke: una introducción a la teoría de categorías, las mónadas de teoría de categorías y su relación con la programación funcional ´
Eugenio Moggi: nociones de computación y mónadas
Pero, ¿por qué una teoría tan abstracta podría ser útil para la programación?
La respuesta es simple: como informáticos, ¡ valoramos la abstracción ! Cuando diseñamos la interfaz para un componente de software, queremos que revele lo menos posible sobre la implementación. Queremos poder reemplazar la implementación con muchas alternativas, muchas otras ''instancias'' del mismo ''concepto''. Cuando diseñamos una interfaz genérica para muchas bibliotecas de programas, es aún más importante que la interfaz que elijamos tenga una variedad de implementaciones. Es la generalidad del concepto de mónada lo que valoramos tanto, porque la teoría de categorías es tan abstracta que sus conceptos son tan útiles para la programación.
No es sorprendente, entonces, que la generalización de las mónadas que presentamos a continuación también tenga una estrecha conexión con la teoría de categorías. Pero hacemos hincapié en que nuestro propósito es muy práctico: no es "implementar la teoría de categorías", es encontrar una forma más general de estructurar las bibliotecas de combinador. ¡Es simplemente nuestra buena suerte que los matemáticos ya hayan hecho gran parte del trabajo por nosotros!
de generalizar mónadas a flechas por John Hughes
Después de haber examinado brevemente a Haskell recientemente, ¿cuál sería una explicación breve, sucinta y práctica de lo que esencialmente es una mónada?
La mayoría de las explicaciones que he encontrado son bastante inaccesibles y carecen de detalles prácticos.
Después de mucho esfuerzo, creo que finalmente entiendo la mónada. Después de releer mi larga crítica de la respuesta abrumadoramente votada, ofreceré esta explicación.
Hay tres preguntas que deben responderse para comprender las mónadas:
- ¿Por qué necesitas una mónada?
- ¿Qué es una mónada?
- ¿Cómo se implementa una mónada?
Como señalé en mis comentarios originales, demasiadas explicaciones de mónada quedan atrapadas en la pregunta número 3, sin, y antes de realmente cubrir adecuadamente la pregunta 2 o la pregunta 1.
¿Por qué necesitas una mónada?
Los lenguajes funcionales puros como Haskell son diferentes de los lenguajes imperativos como C o Java en que un programa funcional puro no se ejecuta necesariamente en un orden específico, un paso a la vez. Un programa Haskell es más parecido a una función matemática, en la que puede resolver la "ecuación" en cualquier número de órdenes potenciales. Esto confiere una serie de beneficios, entre los que se encuentra que elimina la posibilidad de ciertos tipos de errores, particularmente aquellos relacionados con cosas como "estado".
Sin embargo, hay ciertos problemas que no son tan fáciles de resolver con este estilo de programación. Algunas cosas, como la programación de la consola y la E / S de archivos, necesitan que las cosas sucedan en un orden particular, o necesitan mantener el estado. Una forma de abordar este problema es crear un tipo de objeto que represente el estado de un cálculo y una serie de funciones que toman un objeto de estado como entrada y devuelven un nuevo objeto de estado modificado.
Entonces, creemos un valor hipotético de "estado", que represente el estado de una pantalla de consola.exactamente cómo se construye este valor no es importante, pero digamos que es una matriz de caracteres ASCII de longitud de bytes que representa lo que está visible actualmente en la pantalla, y una matriz que representa la última línea de entrada ingresada por el usuario, en pseudocódigo. Hemos definido algunas funciones que toman el estado de la consola, la modifican y devuelven un nuevo estado de la consola.
consolestate MyConsole = new consolestate;
Entonces, para hacer la programación de la consola, pero de una manera puramente funcional, necesitaría anidar muchas llamadas de función dentro de cada una.
consolestate FinalConsole = print(input(print(myconsole, "Hello, what''s your name?")),"hello, %inputbuffer%!");
La programación de esta manera mantiene el estilo funcional "puro", al tiempo que obliga a que se realicen cambios en la consola en un orden particular. Pero probablemente querremos hacer más que unas pocas operaciones a la vez, como en el ejemplo anterior. Las funciones de anidamiento de esa manera comenzarán a ser torpes. Lo que queremos es un código que haga esencialmente lo mismo que el anterior, pero que esté escrito un poco más así:
consolestate FinalConsole = myconsole:
print("Hello, what''s your name?"):
input():
print("hello, %inputbuffer%!");
De hecho, esta sería una forma más conveniente de escribirlo. ¿Cómo hacemos eso sin embargo?
¿Qué es una mónada?
Una vez que tenga un tipo (como consolestate
) que defina junto con un conjunto de funciones diseñadas específicamente para operar en ese tipo, puede convertir todo el paquete de estas cosas en una "mónada" definiendo un operador como :
(vincular) que automáticamente alimenta los valores de retorno a su izquierda, en los parámetros de función a su derecha, y un lift
operador que convierte las funciones normales, en funciones que funcionan con ese tipo específico de operador de enlace.
¿Cómo se implementa una mónada?
Vea otras respuestas, que parecen bastante libres para entrar en detalles.
En realidad, contrario a la comprensión común de las mónadas, no tienen nada que ver con el estado. Las mónadas son simplemente una forma de envolver cosas y proporcionar métodos para realizar operaciones en las cosas envueltas sin desenvolverlas.
Por ejemplo, puede crear un tipo para envolver otro, en Haskell:
data Wrapped a = Wrap a
Para envolver cosas definimos
return :: a -> Wrapped a
return x = Wrap x
Para realizar operaciones sin desenvolver, digamos que tiene una función f :: a -> b
, luego puede hacer esto para levantar esa función para actuar sobre valores envueltos:
fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)
Eso es todo lo que hay que entender. Sin embargo, resulta que hay una función más general para hacer este levantamiento , que es bind
:
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x
bind
puede hacer un poco más que fmap
, pero no al revés. En realidad, fmap
se puede definir en términos de bind
y return
. Entonces, al definir una mónada ... le das su tipo (aquí estaba Wrapped a
) y luego dices cómo funcionan sus operaciones de return
y bind
.
Lo bueno es que esto resulta ser un patrón tan general que aparece por todas partes, el estado de encapsulación de manera pura es solo uno de ellos.
Para un buen artículo sobre cómo las mónadas se pueden usar para introducir dependencias funcionales y así controlar el orden de evaluación, como se usa en la mónada IO de Haskell, consulte IO Inside .
En cuanto a la comprensión de las mónadas, no te preocupes demasiado por eso. Lea sobre ellos lo que le parezca interesante y no se preocupe si no entiende de inmediato. Entonces simplemente bucear en un idioma como Haskell es el camino a seguir. Las mónadas son una de estas cosas en las que la comprensión llega a tu cerebro mediante la práctica, un día te das cuenta de repente de que las entiendes.
Explicar "¿Qué es una mónada?" Es un poco como decir "¿Qué es un número?" Usamos números todo el tiempo. Pero imagina que conociste a alguien que no sabía nada sobre números. ¿Cómo diablos explicarías qué son los números? ¿Y cómo comenzarías a describir por qué eso podría ser útil?
¿Qué es una mónada? La respuesta corta: es una forma específica de encadenar operaciones juntas.
En esencia, está escribiendo pasos de ejecución y vinculándolos con la "función de enlace". (En Haskell, se llama >>=
.) Puede escribir las llamadas al operador de enlace usted mismo, o puede usar la sintaxis sugar que hace que el compilador inserte esas llamadas de función por usted. Pero de cualquier manera, cada paso está separado por una llamada a esta función de enlace.
Entonces la función de enlace es como un punto y coma; Separa los pasos en un proceso. El trabajo de la función de vinculación es tomar la salida del paso anterior y pasarla al siguiente paso.
Eso no suena demasiado difícil, ¿verdad? Pero hay más de un tipo de mónada. ¿Por qué? ¿Cómo?
Bueno, la función de vinculación solo puede tomar el resultado de un paso y pasarlo al siguiente. Pero si eso es "todo" que hace la mónada ... eso en realidad no es muy útil. Y eso es importante de entender: cada mónada útil hace algo más además de ser una mónada. Cada mónada útil tiene un "poder especial", que lo hace único.
(Una mónada que no hace nada especial se llama "mónada de identidad". Más bien como la función de identidad, esto suena como algo completamente inútil, pero resulta que no es ... Pero esa es otra historia ™).
Básicamente, cada mónada tiene su propia implementación de la función de vinculación. Y puede escribir una función de enlace de modo que haga cosas entre los pasos de ejecución. Por ejemplo:
Si cada paso devuelve un indicador de éxito / fracaso, puede hacer que Bind ejecute el siguiente paso solo si el anterior tuvo éxito. De esta manera, un paso fallido anula la secuencia completa "automáticamente", sin ninguna prueba condicional de su parte. (La falla Mónada .)
Extendiendo esta idea, puede implementar "excepciones". (La mónada de error o la mónada de excepción ). Debido a que los está definiendo usted mismo en lugar de ser una característica del lenguaje, puede definir cómo funcionan. (Por ejemplo, tal vez quieras ignorar las dos primeras excepciones y solo abortar cuando se lanza una tercera excepción).
Puede hacer que cada paso devuelva múltiples resultados , y hacer que la función de enlace se repita sobre ellos, alimentando cada uno en el siguiente paso para usted. De esta manera, no tiene que seguir escribiendo bucles por todas partes cuando se trata de múltiples resultados. La función de enlace "automáticamente" hace todo eso por usted. (La Lista Mónada .)
Además de pasar un "resultado" de un paso a otro, también puede hacer que la función de enlace pase datos adicionales . Estos datos ahora no aparecen en su código fuente, pero aún puede acceder a ellos desde cualquier lugar, sin tener que pasarlos manualmente a todas las funciones. (El lector Monad .)
Puede hacerlo para que se puedan reemplazar los "datos adicionales". Esto le permite simular actualizaciones destructivas , sin hacer actualizaciones destructivas. (La mónada estatal y su primo el escritor mónada ).
Debido a que solo está simulando actualizaciones destructivas, puede hacer cosas triviales que serían imposibles con actualizaciones destructivas reales . Por ejemplo, puede deshacer la última actualización o volver a una versión anterior .
Puede hacer una mónada donde los cálculos se pueden pausar , por lo que puede pausar su programa, entrar y jugar con los datos de estado internos, y luego reanudarlo.
Puede implementar "continuaciones" como una mónada. ¡Esto te permite romper las mentes de las personas!
Todo esto y más es posible con las mónadas. Por supuesto, todo esto también es perfectamente posible sin mónadas. Es drásticamente más fácil usar mónadas.
Pero, ¡podrías haber inventado mónadas!
sigfpe dice:
Pero todo esto introduce a las mónadas como algo esotérico que necesita explicación. Pero lo que quiero argumentar es que no son esotéricos en absoluto. De hecho, ante varios problemas en la programación funcional, habría sido llevado, inexorablemente, a ciertas soluciones, todas las cuales son ejemplos de mónadas. De hecho, espero que los inventes ahora si aún no lo has hecho. Entonces es un pequeño paso notar que todas estas soluciones son, de hecho, la misma solución disfrazada. Y después de leer esto, es posible que esté en una mejor posición para comprender otros documentos sobre mónadas porque reconocerá todo lo que ve como algo que ya ha inventado.
Muchos de los problemas que las mónadas intentan resolver están relacionados con el tema de los efectos secundarios. Entonces comenzaremos con ellos. (Tenga en cuenta que las mónadas le permiten hacer más que manejar los efectos secundarios, en particular muchos tipos de objeto contenedor pueden verse como mónadas. Algunas de las introducciones a las mónadas tienen dificultades para conciliar estos dos usos diferentes de las mónadas y concentrarse en solo uno o el otro.)
En un lenguaje de programación imperativo como C ++, las funciones no se comportan como las funciones de las matemáticas. Por ejemplo, supongamos que tenemos una función C ++ que toma un único argumento de coma flotante y devuelve un resultado de coma flotante. Superficialmente, puede parecer un poco como una función matemática que asigna reales a reales, pero una función C ++ puede hacer más que devolver un número que depende de sus argumentos. Puede leer y escribir los valores de las variables globales, así como escribir la salida en la pantalla y recibir la entrada del usuario. Sin embargo, en un lenguaje funcional puro, una función solo puede leer lo que se le proporciona en sus argumentos y la única forma en que puede tener un efecto en el mundo es a través de los valores que devuelve.
Primero debes entender qué es un functor. Antes de eso, comprenda las funciones de orden superior.
Una función de orden superior es simplemente una función que toma una función como argumento.
Un functor es cualquier construcción de tipo T
para la cual existe una función de orden superior, llamada map
, que transforma una función de tipo a -> b
(dados dos tipos a
y b
) en una función T a -> T b
. Esta función de map
también debe obedecer las leyes de identidad y composición de tal manera que las siguientes expresiones devuelvan verdaderas para todos p
y q
(notación Haskell):
map id = id
map (p . q) = map p . map q
Por ejemplo, un constructor de tipo llamado List
es un functor si viene equipado con una función de tipo (a -> b) -> List a -> List b
que obedece las leyes anteriores. La única implementación práctica es obvia. La función resultante List a -> List b
itera sobre la lista dada, llamando a la función (a -> b)
para cada elemento, y devuelve la lista de resultados.
Una mónada es esencialmente un functor T
con dos métodos adicionales, join
, de tipo T (T a) -> T a
, y unit
(a veces llamado return
, fork
, o pure
) de tipo a -> T a
. Para listas en Haskell:
join :: [[a]] -> [a]
pure :: a -> [a]
¿Por qué es útil? Porque podría, por ejemplo, map
una lista con una función que devuelve una lista. Join
toma la lista resultante de listas y las concatena. List
es una mónada porque esto es posible.
Puede escribir una función que haga un map
y luego join
. Esta función se llama bind
, o flatMap
, o (>>=)
, o (=<<)
. Así es normalmente como se da una instancia de mónada en Haskell.
Una mónada tiene que cumplir ciertas leyes, a saber, que la join
debe ser asociativa. Esto significa que si tiene un valor x
de tipo [[[a]]]
entonces join (join x)
debería ser igual a join (map join x)
. Y pure
debe ser una identidad para join
modo que join (pure x) == x
.
Primero: el término mónada es un poco vacío si no eres matemático. Un término alternativo es generador de cómputo, que es un poco más descriptivo de para qué son realmente útiles.
Pides ejemplos prácticos:
Ejemplo 1: comprensión de la lista :
[x*2 | x<-[1..10], odd x]
Esta expresión devuelve los dobles de todos los números impares en el rango de 1 a 10. ¡Muy útil!
Resulta que esto es solo azúcar sintáctico para algunas operaciones dentro de la mónada List. La misma lista de comprensión se puede escribir como:
do
x <- [1..10]
guard (odd x)
return (x * 2)
O incluso:
[1..10] >>= (/x -> guard (odd x) >> return (x*2))
Ejemplo 2: Entrada / Salida :
do
putStrLn "What is your name?"
name <- getLine
putStrLn ("Welcome, " ++ name ++ "!")
Ambos ejemplos usan mónadas, constructores de cálculo AKA. El tema común es que la mónada encadena las operaciones de alguna manera específica y útil. En la comprensión de la lista, las operaciones se encadenan de tal manera que si una operación devuelve una lista, las siguientes operaciones se realizan en cada elemento de la lista. Por otro lado, la mónada IO realiza las operaciones secuencialmente, pero pasa una "variable oculta", que representa "el estado del mundo", que nos permite escribir código de E / S de una manera puramente funcional.
Resulta que el patrón de operaciones de encadenamiento es bastante útil y se utiliza para muchas cosas diferentes en Haskell.
Otro ejemplo son las excepciones: al usar la mónada Error
, las operaciones se encadenan de tal manera que se realizan de forma secuencial, excepto si se produce un error, en cuyo caso se abandona el resto de la cadena.
Tanto la sintaxis de comprensión de la lista como la notación do son azúcar sintáctica para encadenar operaciones utilizando el operador >>=
. Una mónada es básicamente un tipo que admite el operador >>=
.
Ejemplo 3: un analizador sintáctico
Este es un analizador muy simple que analiza una cadena entre comillas o un número:
parseExpr = parseString <|> parseNumber
parseString = do
char ''"''
x <- many (noneOf "/"")
char ''"''
return (StringValue x)
parseNumber = do
num <- many1 digit
return (NumberValue (read num))
Las operaciones char
, digit
, etc. son bastante simples. O coinciden o no coinciden. La magia es la mónada que gestiona el flujo de control: las operaciones se realizan de forma secuencial hasta que falla una coincidencia, en cuyo caso la mónada retrocede al último <|>
e intenta la siguiente opción. Una vez más, una forma de encadenar operaciones con algunas semánticas adicionales útiles.
Ejemplo 4: programación asincrónica
Los ejemplos anteriores están en Haskell, pero resulta que F# también admite mónadas. Este ejemplo es robado de Don Syme :
let AsyncHttp(url:string) =
async { let req = WebRequest.Create(url)
let! rsp = req.GetResponseAsync()
use stream = rsp.GetResponseStream()
use reader = new System.IO.StreamReader(stream)
return reader.ReadToEnd() }
Este método busca una página web. La línea de perforación es el uso de GetResponseAsync
: en realidad espera la respuesta en un hilo separado, mientras que el hilo principal regresa de la función. Las últimas tres líneas se ejecutan en el hilo generado cuando se ha recibido la respuesta.
En la mayoría de los otros idiomas, tendría que crear explícitamente una función separada para las líneas que manejan la respuesta. La mónada async
es capaz de "dividir" el bloque por sí solo y posponer la ejecución de la segunda mitad. (La sintaxis async {}
indica que el flujo de control en el bloque está definido por la mónada async
).
Cómo trabajan ellos
Entonces, ¿cómo puede una mónada hacer todas estas cosas elegantes de flujo de control? Lo que realmente sucede en un do-block (o una expresión de cálculo como se los llama en F #), es que cada operación (básicamente cada línea) está envuelta en una función anónima separada. Estas funciones se combinan utilizando el operador de bind
(escrito >>=
en Haskell). Dado que la operación de bind
combina funciones, puede ejecutarlas como mejor le parezca: secuencialmente, varias veces, a la inversa, descarte algunas, ejecute algunas en un hilo separado cuando lo desee y así sucesivamente.
Como ejemplo, esta es la versión ampliada del código IO del ejemplo 2:
putStrLn "What is your name?"
>>= (/_ -> getLine)
>>= (/name -> putStrLn ("Welcome, " ++ name ++ "!"))
Esto es más feo, pero también es más obvio lo que realmente está sucediendo. El operador >>=
es el ingrediente mágico: toma un valor (en el lado izquierdo) y lo combina con una función (en el lado derecho) para producir un nuevo valor. Este nuevo valor es tomado por el siguiente operador >>=
y nuevamente combinado con una función para producir un nuevo valor. >>=
se puede ver como un mini evaluador.
Tenga en cuenta que >>=
está sobrecargado para diferentes tipos, por lo que cada mónada tiene su propia implementación de >>=
. (Sin embargo, todas las operaciones en la cadena deben ser del tipo de la misma mónada; de lo contrario, el operador >>=
no funcionará).
La implementación más simple posible de >>=
simplemente toma el valor a la izquierda y lo aplica a la función a la derecha y devuelve el resultado, pero como se dijo antes, lo que hace que todo el patrón sea útil es cuando hay algo extra en el implementación de mónada de >>=
.
Existe cierta inteligencia adicional en cómo se pasan los valores de una operación a la siguiente, pero esto requiere una explicación más profunda del sistema de tipo Haskell.
Resumiendo
En términos de Haskell, una mónada es un tipo parametrizado que es una instancia de la clase de tipo Mónada, que define >>=
junto con algunos otros operadores. En términos simples, una mónada es solo un tipo para el que se define la operación >>=
.
En sí >>=
es solo una forma engorrosa de encadenar funciones, pero con la presencia de la notación que oculta la "fontanería", las operaciones monádicas resultan ser una abstracción muy agradable y útil, útil en muchos lugares del lenguaje. y útil para crear sus propios mini idiomas en el idioma.
¿Por qué son duras las mónadas?
Para muchos estudiantes de Haskell, las mónadas son un obstáculo que golpean como una pared de ladrillos. No es que las mónadas en sí mismas sean complejas, sino que la implementación se basa en muchas otras características avanzadas de Haskell como tipos parametrizados, clases de tipos, etc. El problema es que Haskell I / O se basa en mónadas, y I / O es probablemente una de las primeras cosas que quieres entender cuando aprendes un nuevo idioma; después de todo, no es muy divertido crear programas que no produzcan salida. No tengo una solución inmediata para este problema de huevo y gallina, excepto tratar la E / S como "la magia sucede aquí" hasta que tenga suficiente experiencia con otras partes del lenguaje. Lo siento.
Excelente blog sobre mónadas: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
Una mónada es un tipo de datos que tiene dos operaciones: >>=
(también conocido como bind
) y return
(también conocido como unit
). return
toma un valor arbitrario y crea una instancia de la mónada con él. >>=
toma una instancia de la mónada y asigna una función sobre ella. (Ya puede ver que una mónada es un tipo de datos extraño, ya que en la mayoría de los lenguajes de programación no se puede escribir una función que tome un valor arbitrario y cree un tipo a partir de ella. Las mónadas usan un tipo de polimorfismo paramétrico ).
En notación Haskell, la interfaz de mónada está escrita
class Monad m where
return :: a -> m a
(>>=) :: forall a b . m a -> (a -> m b) -> m b
Se supone que estas operaciones deben obedecer ciertas "leyes", pero eso no es extremadamente importante: las "leyes" simplemente codifican la forma en que las implementaciones sensatas de las operaciones deberían comportarse (básicamente, que >>=
y return
deberían estar de acuerdo sobre cómo se transforman los valores en instancias de mónada y que >>=
es asociativo).
Las mónadas no son solo sobre estado y E / S: resumen un patrón común de cómputo que incluye trabajar con estado, E / S, excepciones y no determinismo. Probablemente las mónadas más simples de entender son las listas y los tipos de opciones:
instance Monad [ ] where
[] >>= k = []
(x:xs) >>= k = k x ++ (xs >>= k)
return x = [x]
instance Monad Maybe where
Just x >>= k = k x
Nothing >>= k = Nothing
return x = Just x
donde []
y :
son los constructores de la lista, ++
es el operador de concatenación, y Just
and Nothing
son los constructores de Maybe
. Ambas mónadas encapsulan patrones de cálculo comunes y útiles en sus respectivos tipos de datos (tenga en cuenta que ninguno tiene nada que ver con los efectos secundarios o E / S).
Realmente tienes que jugar escribiendo un código Haskell no trivial para apreciar de qué se tratan las mónadas y por qué son útiles.
[Descargo de responsabilidad: todavía estoy tratando de asimilar por completo a las mónadas. Lo siguiente es justo lo que he entendido hasta ahora. Si está mal, espero que alguien con conocimiento me llame a la alfombra.]
Arnar escribió:
Las mónadas son simplemente una forma de envolver cosas y proporcionar métodos para realizar operaciones en las cosas envueltas sin desenvolverlas.
Eso es precisamente eso. La idea es así:
Tomas algún tipo de valor y lo envuelves con información adicional. Al igual que el valor es de cierto tipo (por ejemplo, un entero o una cadena), la información adicional es de cierto tipo.
Por ejemplo, esa información adicional podría ser un
Maybe
o unIO
.Luego tiene algunos operadores que le permiten operar con los datos empaquetados mientras transporta esa información adicional. Estos operadores usan la información adicional para decidir cómo cambiar el comportamiento de la operación en el valor envuelto.
Por ejemplo, un
Maybe Int
puede ser unJust Int
oNothing
. Ahora, si agrega unMaybe Int
a unMaybe Int
, el operador verificará si ambos están dentro deJust Int
y, de ser así, desenvolverá losInt
s, pasará el operador de suma y volverá a envolver elInt
resultante en un nuevoJust Int
(que es unMaybe Int
válido) y, por lo tanto, devuelve unMaybe Int
. Pero si uno de ellos era unNothing
dentro, este operador devolverá inmediatamenteNothing
, que nuevamente es unMaybe Int
. De esa manera, puede fingir que susMaybe Int
son solo números normales y realizar cálculos matemáticos regulares sobre ellos. Si obtuvieras unNothing
, tus ecuaciones seguirán produciendo el resultado correcto, sin que tengas que tirar cheques porNothing
todas partes .
Pero el ejemplo es justo lo que sucede para Maybe
. Si la información adicional fuera un IO
, entonces se llamaría a ese operador especial definido para IO
s, y podría hacer algo totalmente diferente antes de realizar la adición. (OK, agregar dos IO Int
s juntos probablemente no tenga sentido, aún no estoy seguro). (Además, si prestó atención al ejemplo de Maybe
, habrá notado que "ajustar un valor con cosas adicionales" no siempre es correcto. Pero es difícil ser exacto, correcto y preciso sin ser inescrutable).
Básicamente, "mónada" significa más o menos "patrón" . Pero en lugar de un libro lleno de Patrones explicados informalmente y específicamente nombrados, ahora tiene una construcción de lenguaje , sintaxis y todo, que le permite declarar nuevos patrones como elementos en su programa . (La imprecisión aquí es que todos los patrones tienen que seguir una forma particular, por lo que una mónada no es tan genérica como un patrón. Pero creo que ese es el término más cercano que la mayoría de la gente conoce y entiende).
Y es por eso que la gente encuentra a las mónadas tan confusas: porque son un concepto tan genérico. Preguntar qué hace que algo sea una mónada es igualmente vago como preguntar qué hace que algo sea un patrón.
Pero piense en las implicaciones de tener un soporte sintáctico en el lenguaje para la idea de un patrón: en lugar de tener que leer el libro Gang of Four y memorizar la construcción de un patrón en particular, simplemente escriba código que implemente este patrón de manera agnóstica, forma genérica una vez y ya está! Luego puede reutilizar este patrón, como Visitor o Strategy o Façade o lo que sea, simplemente decorando las operaciones en su código con él, ¡sin tener que volver a implementarlo una y otra vez!
Es por eso que las personas que entienden las mónadas las encuentran tan útiles : no es un concepto de torre de marfil del que los snobs intelectuales se enorgullecen de comprender (OK, eso también, por supuesto, teehee), sino que en realidad simplifica el código.
http://code.google.com/p/monad-tutorial/ es un trabajo en progreso para abordar exactamente esta pregunta.
Las mónadas no son metáforas , sino una abstracción prácticamente útil que emerge de un patrón común, como explica Daniel Spiewak.
(Ver también las respuestas en ¿Qué es una mónada? )
¡Una buena motivación para las mónadas es sigfpe (Dan Piponi), ¡ Podrías haber inventado mónadas! (Y tal vez ya lo tengas) . Hay MUCHOS otros tutoriales de mónadas , muchos de los cuales tratan erróneamente de explicar las mónadas en "términos simples" usando varias analogías: esta es la falacia del tutorial de mónada ; Evítales.
Como dice DR MacIver en Cuéntanos por qué apesta tu idioma :
Entonces, cosas que odio de Haskell:
Comencemos con lo obvio. Mónada tutoriales. No, no mónadas. Específicamente los tutoriales. Son interminables, exagerados y querido Dios, son tediosos. Además, nunca he visto ninguna evidencia convincente de que realmente ayuden. Lea la definición de clase, escriba un código, supere el nombre aterrador.
¿Dices que entiendes a la Mónada Quizás? Bien, ya estás en camino. Simplemente comience a usar otras mónadas y, tarde o temprano, comprenderá qué son las mónadas en general.
[Si está orientado matemáticamente, es posible que desee ignorar las docenas de tutoriales y aprender la definición, o seguir conferencias en teoría de categorías :) La parte principal de la definición es que una Mónada M involucra un "constructor de tipos" que define para cada tipo existente "T", un nuevo tipo "MT", y algunas formas de ir y venir entre los tipos "normales" y los tipos "M".]
Además, sorprendentemente, una de las mejores introducciones a las mónadas es en realidad uno de los primeros trabajos académicos que presenta a las mónadas, las mónadas de Philip Wadler para la programación funcional . En realidad, tiene ejemplos prácticos y no triviales de motivación, a diferencia de muchos de los tutoriales artificiales que existen.
Después de responder a esta pregunta hace unos años, creo que puedo mejorar y simplificar esa respuesta con ...
Una mónada es una técnica de composición de funciones que externaliza el tratamiento para algunos escenarios de entrada utilizando una función de composición bind
, para preprocesar la entrada durante la composición.
En composición normal, la función, compose (>>)
se usa para aplicar la función compuesta al resultado de su predecesor en secuencia. Es importante destacar que la función que se compone es necesaria para manejar todos los escenarios de su entrada.
(x -> y) >> (y -> z)
Este diseño se puede mejorar reestructurando la entrada para que los estados relevantes se interroguen más fácilmente. Entonces, en lugar de simplemente y
el valor puede convertirse Mb
en, por ejemplo, (is_OK, b)
si se y
incluye una noción de validez.
Por ejemplo, cuando la entrada es solamente posiblemente un número, en lugar de devolver una cadena que puede contener diligentemente contener un número o no, usted podría reestructurar el tipo en un bool
indicando la presencia de un número válido y un número en tupla tal como, bool * float
. Las funciones compuestas ya no necesitarían analizar una cadena de entrada para determinar si existe un número, sino que simplemente podrían inspeccionar la bool
parte de una tupla.
(Ma -> Mb) >> (Mb -> Mc)
Aquí, una vez más, la composición ocurre naturalmente con, compose
por lo que cada función debe manejar todos los escenarios de su entrada individualmente, aunque hacerlo ahora es mucho más fácil.
Sin embargo, ¿qué pasaría si pudiéramos externalizar el esfuerzo de interrogación para aquellos momentos en los que manejar un escenario es una rutina? Por ejemplo, ¿qué pasa si nuestro programa no hace nada cuando la entrada no está bien como en cuando lo is_OK
está false
? Si eso se hiciera, las funciones compuestas no tendrían que manejar ese escenario por sí mismas, simplificando drásticamente su código y logrando otro nivel de reutilización.
Para lograr esta externalización podríamos usar una función bind (>>=)
,, para realizar el en composition
lugar de compose
. Como tal, en lugar de simplemente transferir valores de la salida de una función a la entrada de otra Bind
, inspeccionaría la M
porción Ma
y decidiría si aplicar la función compuesta a la función compuesta y cómo a
. Por supuesto, la función bind
se definiría específicamente para nuestro particular a M
fin de poder inspeccionar su estructura y realizar cualquier tipo de aplicación que queramos. No obstante, a
puede ser cualquier cosa, ya que bind
simplemente pasa lo no a
inspeccionado a la función compuesta cuando determina la aplicación necesaria. Además, las funciones compuestas en sí mismas ya no necesitan lidiar conM
parte de la estructura de entrada tampoco, simplificándolas. Por lo tanto...
(a -> Mb) >>= (b -> Mc)
o más sucintamente Mb >>= (b -> Mc)
En resumen, una mónada se externaliza y, por lo tanto, proporciona un comportamiento estándar en torno al tratamiento de ciertos escenarios de entrada una vez que la entrada se diseña para exponerlos suficientemente. Este diseño es un shell and content
modelo en el que el shell contiene datos relevantes para la aplicación de la función compuesta y es interrogado y solo está disponible para la bind
función.
Por lo tanto, una mónada es tres cosas:
- un
M
caparazón para contener información relevante de mónada - una
bind
función implementada para hacer uso de esta información de shell en su aplicación de las funciones compuestas a los valores de contenido que encuentra dentro del shell, y - funciones componibles de la forma,
a -> Mb
produciendo resultados que incluyen datos de gestión monádica.
En términos generales, la entrada a una función es mucho más restrictiva que su salida, que puede incluir cosas como condiciones de error; por lo tanto, la Mb
estructura de resultados es generalmente muy útil. Por ejemplo, el operador de división no devuelve un número cuando el divisor es 0
.
Además, monad
s puede incluir funciones de ajuste que envuelven valores, a
en el tipo monádico Ma
, y funciones generales a -> b
, en funciones monádicas a -> Mb
, ajustando sus resultados después de la aplicación. Por supuesto, como bind
, tales funciones de ajuste son específicas de M
. Un ejemplo:
let return a = [a]
let lift f a = return (f a)
El diseño de la bind
función supone estructuras de datos inmutables y funciones puras, otras cosas se vuelven complejas y no se pueden hacer garantías. Como tal, hay leyes monádicas:
Dado...
M_
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)
Luego...
Left Identity : (return a) >>= f === f a
Right Identity : Ma >>= return === Ma
Associative : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)
Associativity
significa que bind
conserva el orden de evaluación independientemente de cuándo bind
se aplique. Es decir, en la definición de Associativity
arriba, la evaluación temprana forzada del paréntesis binding
de f
y g
solo dará como resultado una función que se espera Ma
para completar el bind
. Por lo tanto, la evaluación de Ma
debe determinarse antes de que su valor pueda aplicarse f
y ese resultado a su vez se aplique g
.
En la práctica, monad es una implementación personalizada del operador de composición de funciones que se encarga de los efectos secundarios y los valores de entrada y retorno incompatibles (para el encadenamiento).
Esta respuesta comienza con un ejemplo motivador, funciona a través del ejemplo, deriva un ejemplo de una mónada y define formalmente "mónada".
Considere estas tres funciones en pseudocódigo:
f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x) := <x, "">
f
toma un par ordenado del formulario <x, messages>
y devuelve un par ordenado. Deja el primer elemento intacto y se agrega "called f. "
al segundo elemento. Lo mismo con g
.
Puede componer estas funciones y obtener su valor original, junto con una cadena que muestra en qué orden se llamaron las funciones:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">
Que no le gusta el hecho de que f
y g
es responsable de sus propios añadiendo mensajes de registro de la información de registro anterior. (Solo imagínelo por el argumento de que, en lugar de agregar cadenas, f
y g
debe realizar una lógica complicada en el segundo elemento del par. Sería un dolor repetir esa lógica complicada en dos, o más, funciones diferentes).
Prefieres escribir funciones más simples:
f(x) := <x, "called f. ">
g(x) := <x, "called g. ">
wrap(x) := <x, "">
Pero mira lo que sucede cuando los compones:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">
El problema es que pasar un par a una función no te da lo que quieres. Pero, ¿y si pudieras alimentar un par en una función?
feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">
Leer feed(f, m)
como "alimentar m
en f
". Para alimentar a una pareja <x, messages>
en una función f
es a pasar x
en f
, obtener <y, message>
fuera de f
, y volver <y, messages message>
.
feed(f, <x, messages>) := let <y, message> = f(x)
in <y, messages message>
Observe lo que sucede cuando hace tres cosas con sus funciones:
Primero: si ajusta un valor y luego alimenta el par resultante en una función:
feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
in <y, "" message>
= let <y, message> = <x, "called f. ">
in <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)
Eso es lo mismo que pasar el valor a la función.
Segundo: si alimentas un par en wrap
:
feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
in <y, messages message>
= let <y, message> = <x, "">
in <y, messages message>
= <x, messages "">
= <x, messages>
Eso no cambia la pareja.
Tercero: si se define una función que toma x
y se alimenta g(x)
en f
:
h(x) := feed(f, g(x))
y alimentar un par en él:
feed(h, <x, messages>)
= let <y, message> = h(x)
in <y, messages message>
= let <y, message> = feed(f, g(x))
in <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
in <y, messages message>
= let <y, message> = let <z, msg> = f(x)
in <z, "called g. " msg>
in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
in <z, "called g. " msg>
in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))
Es lo mismo que alimentar al par g
y alimentar al par resultante f
.
Tienes la mayor parte de una mónada. Ahora solo necesita saber acerca de los tipos de datos en su programa.
¿Qué tipo de valor es <x, "called f. ">
? Bueno, eso depende de qué tipo de valor x
sea. Si x
es de tipo t
, entonces su par es un valor de tipo "par de t
y cadena". Llama a ese tipo M t
.
M
es un constructor de tipos: M
solo no se refiere a un tipo, sino que se M _
refiere a un tipo una vez que completa el espacio en blanco con un tipo. Un M int
es un par de int y una cadena. Un M string
es un par de una cadena y una cadena. Etc.
¡Felicitaciones, has creado una mónada!
Formalmente, tu mónada es la tupla <M, feed, wrap>
.
Una mónada es una tupla <M, feed, wrap>
donde:
-
M
es un constructor de tipos. -
feed
toma a (función que toma at
y devuelve anM u
) y anM t
y devuelve anM u
. -
wrap
toma unv
y devuelve unM v
.
t
, u
y v
son tres tipos que pueden ser o no iguales. Una mónada satisface las tres propiedades que probó para su mónada específica:
Alimentar un envoltorio
t
en una función es lo mismo que pasar el envoltoriot
a la función.Formalmente:
feed(f, wrap(x)) = f(x)
La alimentación de un
M t
awrap
no hace nada para elM t
.Formalmente:
feed(wrap, m) = m
Alimentando un
M t
(llámelom
) en una función que- pasa
t
alg
- recibe un
M u
(llámalon
) deg
- alimenta
n
enf
es lo mismo que
- alimentando
m
ag
- obteniendo
n
deg
- alimentando
n
af
Formalmente:
feed(h, m) = feed(f, feed(g, m))
dondeh(x) := feed(f, g(x))
- pasa
Por lo general, feed
se llama bind
(AKA >>=
en Haskell) y wrap
se llama return
.
Las dos cosas que me ayudaron mejor al aprender allí fueron:
Capítulo 8, "Analizadores funcionales", del libro de Graham Hutton Programming in Haskell . En realidad, esto no menciona a las mónadas, pero si puede trabajar en el capítulo y comprender realmente todo lo que contiene, particularmente cómo se evalúa una secuencia de operaciones de enlace, comprenderá las partes internas de las mónadas. Espere que esto tome varios intentos.
El tutorial Todo sobre las mónadas . Esto da varios buenos ejemplos de su uso, y debo decir que la analogía en Appendex funcionó para mí.
Las mónadas deben controlar el flujo de los tipos de datos abstractos para los datos.
En otras palabras, muchos desarrolladores se sienten cómodos con la idea de conjuntos, listas, diccionarios (o hashes o mapas) y árboles. Dentro de esos tipos de datos hay muchos casos especiales (por ejemplo, InsertionOrderPreservingIdentityHashMap).
Sin embargo, cuando se enfrentan con el "flujo" del programa, muchos desarrolladores no han estado expuestos a muchas más construcciones que if, switch / case, do, while, goto (grr) y (quizás) cierres.
Entonces, una mónada es simplemente una construcción de flujo de control. Una mejor frase para reemplazar a la mónada sería ''tipo de control''.
Como tal, una mónada tiene ranuras para la lógica de control, o declaraciones o funciones: el equivalente en las estructuras de datos sería decir que algunas estructuras de datos le permiten agregar datos y eliminarlos.
Por ejemplo, la mónada "if":
if( clause ) then block
en su forma más simple tiene dos espacios: una cláusula y un bloque. La if
mónada generalmente se construye para evaluar el resultado de la cláusula y, si no es falso, evalúa el bloque. Muchos desarrolladores no son introducidos a las mónadas cuando aprenden ''si'', y simplemente no es necesario comprender a las mónadas para escribir una lógica efectiva.
Las mónadas pueden volverse más complicadas, de la misma manera que las estructuras de datos pueden volverse más complicadas, pero hay muchas categorías amplias de mónadas que pueden tener una semántica similar, pero diferentes implementaciones y sintaxis.
Por supuesto, de la misma manera que las estructuras de datos pueden iterarse o atravesarse, las mónadas pueden evaluarse.
Los compiladores pueden o no tener soporte para mónadas definidas por el usuario. Haskell ciertamente lo hace. Ioke tiene algunas capacidades similares, aunque el término mónada no se usa en el lenguaje.
Lo que el mundo necesita es otra publicación de blog de mónada, pero creo que esto es útil para identificar mónadas existentes en la naturaleza.
Lo anterior es un fractal llamado triángulo de Sierpinski, el único fractal que puedo recordar dibujar. Los fractales son estructuras auto-similares como el triángulo anterior, en el cual las partes son similares al todo (en este caso, exactamente la mitad de la escala como triángulo padre).
Las mónadas son fractales. Dada una estructura de datos monádica, sus valores se pueden componer para formar otro valor de la estructura de datos. Es por eso que es útil para la programación, y es por eso que ocurre en muchas situaciones.
Mi tutorial favorito de Monad:
http://www.haskell.org/haskellwiki/All_About_Monads
(¡de 170,000 visitas en una búsqueda en Google de "mónada tutorial"!)
@Stu: El objetivo de las mónadas es permitirle agregar (generalmente) semántica secuencial a un código puro; incluso puede componer mónadas (usando Transformadores de mónada) y obtener una semántica combinada más interesante y complicada, como el análisis con manejo de errores, estado compartido y registro, por ejemplo. Todo esto es posible en código puro, las mónadas solo le permiten abstraerlo y reutilizarlo en bibliotecas modulares (siempre bueno en programación), así como proporcionar una sintaxis conveniente para que parezca imprescindible.
Haskell ya tiene una sobrecarga de operadores [1]: utiliza clases de tipos de la misma manera que uno podría usar interfaces en Java o C #, pero resulta que Haskell también permite tokens no alfanuméricos como + && y> como identificadores de infijo. Es solo sobrecarga del operador en su forma de verlo si quiere decir "sobrecargar el punto y coma" [2]. Suena como magia negra y pide problemas para "sobrecargar el punto y coma" (los piratas informáticos emprendedores de Perl se enteran de esta idea) pero el punto es que sin mónadas no hay punto y coma, ya que el código puramente funcional no requiere ni permite una secuencia explícita.
Todo esto suena mucho más complicado de lo necesario. El artículo de sigfpe es bastante bueno, pero utiliza a Haskell para explicarlo, lo que no logra resolver el problema del huevo y la gallina de entender a Haskell para mimar a las mónadas y entender a las mónadas para que las mimeticen.
[1] Este es un problema separado de las mónadas, pero las mónadas utilizan la función de sobrecarga del operador de Haskell.
[2] Esto también es una simplificación excesiva ya que el operador para encadenar acciones monádicas es >> = (pronunciado "bind") pero hay un azúcar sintáctico ("do") que le permite usar llaves y puntos y comas y / o sangría y líneas nuevas.
Además de las excelentes respuestas anteriores, permítame ofrecerle un enlace al siguiente artículo (de Patrick Thomson) que explica las mónadas relacionando el concepto con la biblioteca JavaScript jQuery (y su forma de usar el "encadenamiento de métodos" para manipular el DOM) : jQuery es una mónada
La documentación de jQuery en sí no se refiere al término "mónada", sino que habla sobre el "patrón de construcción", que probablemente sea más familiar. Esto no cambia el hecho de que tienes una mónada adecuada allí quizás sin siquiera darte cuenta.
Deje que el " {| a |m}
" siguiente represente algún dato monádico. Un tipo de datos que anuncia un a
:
(I got an a!)
/
{| a |m}
La función, f
sabe cómo crear una mónada, si solo tuviera un a
:
(Hi f! What should I be?)
/
(You?. Oh, you''ll be /
that data there.) /
/ / (I got a b.)
| -------------- |
| / |
f a |
|--later-> {| b |m}
Aquí vemos la función, f
intenta evaluar una mónada pero es reprendido.
(Hmm, how do I get that a?)
o (Get lost buddy.
o Wrong type.)
o /
f {| a |m}
Funtion, f
encuentra una forma de extraer el a
mediante >>=
.
(Muaahaha. How you
like me now!?)
(Better.) /
| (Give me that a.)
(Fine, well ok.) |
/ |
{| a |m} >>= f
Poco f
sabe, la mónada y >>=
están en colusión.
(Yah got an a for me?)
(Yeah, but hey |
listen. I got |
something to |
tell you first |
...) / /
| /
{| a |m} >>= f
¿Pero de qué hablan realmente? Bueno, eso depende de la mónada. Hablar únicamente en abstracto tiene un uso limitado; tienes que tener alguna experiencia con mónadas particulares para desarrollar el entendimiento.
Por ejemplo, el tipo de datos Quizás
data Maybe a = Nothing | Just a
tiene una instancia de mónada que actuará de la siguiente manera ...
En donde, si el caso es Just a
(Yah what is it?)
(... hm? Oh, |
forget about it. |
Hey a, yr up.) |
/ |
(Evaluation / |
time already? / |
Hows my hair?) | |
| / |
| (It''s |
| fine.) /
| / /
{| a |m} >>= f
Pero para el caso de Nothing
(Yah what is it?)
(... There |
is no a. ) |
| (No a?)
(No a.) |
| (Ok, I''ll deal
| with this.)
/ |
/ (Hey f, get lost.)
/ | ( Where''s my a?
/ | I evaluate a)
/ (Not any more |
/ you don''t. |
| We''re returning
| Nothing.) /
| | /
| | /
| | /
{| a |m} >>= f (I got a b.)
| (This is /
| such a /
| sham.) o o /
| o|
|--later-> {| b |m}
Entonces, la mónada tal vez permite que un cálculo continúe si realmente contiene lo a
que anuncia, pero aborta el cálculo si no lo hace. El resultado, sin embargo, sigue siendo un dato monádico, aunque no el resultado de f
. Por esta razón, la mónada Quizás se usa para representar el contexto del fracaso.
Las diferentes mónadas se comportan de manera diferente. Las listas son otros tipos de datos con instancias monádicas. Se comportan de la siguiente manera:
(Ok, here''s your a. Well, its
a bunch of them, actually.)
|
| (Thanks, no problem. Ok
| f, here you go, an a.)
| |
| | (Thank''s. See
| | you later.)
| (Whoa. Hold up f, |
| I got another |
| a for you.) |
| | (What? No, sorry.
| | Can''t do it. I
| | have my hands full
| | with all these "b"
| | I just made.)
| (I''ll hold those, |
| you take this, and /
| come back for more /
| when you''re done /
| and we''ll do it /
| again.) /
/ | ( Uhhh. All right.)
/ | /
/ / /
{| a |m} >>= f
En este caso, la función sabía cómo hacer una lista a partir de su entrada, pero no sabía qué hacer con entradas adicionales y listas adicionales. El enlace >>=
, ayudado f
al combinar las múltiples salidas. Incluyo este ejemplo para mostrar que, si bien >>=
es responsable de la extracción a
, también tiene acceso a la salida eventual de f
. De hecho, nunca extraerá ninguno a a
menos que sepa que el resultado final tiene el mismo tipo de contexto.
Hay otras mónadas que se utilizan para representar diferentes contextos. Aquí hay algunas caracterizaciones de algunas más. La IO
mónada en realidad no tiene un a
, pero conoce a un chico y lo conseguirá a
por ti. La State st
mónada tiene un alijo secreto de st
que pasará f
debajo de la mesa, a pesar de que f
acaba de llegar pidiendo un a
. La Reader r
mónada es similar a State st
, a pesar de que sólo deja f
ver r
.
El punto en todo esto es que cualquier tipo de datos que se declare a sí mismo como una mónada está declarando algún tipo de contexto en torno a la extracción de un valor de la mónada. ¿La gran ganancia de todo esto? Bueno, es bastante fácil armar un cálculo con algún tipo de contexto. Sin embargo, puede volverse desordenado al unir varios cálculos cargados de contexto. Las operaciones de mónada se encargan de resolver las interacciones de contexto para que el programador no tenga que hacerlo.
Tenga en cuenta que el uso de >>=
alivia un desastre al quitarle algo de autonomía f
. Es decir, en el caso anterior, Nothing
por ejemplo, f
ya no se puede decidir qué hacer en el caso de Nothing
; se codifica en >>=
. Esta es la compensación. Si fue necesario f
decidir qué hacer en el caso de Nothing
, entonces f
debería haber sido una función desde Maybe a
hasta Maybe b
. En este caso, Maybe
ser una mónada es irrelevante.
Sin embargo, tenga en cuenta que a veces un tipo de datos no exporta sus constructores (mirándolo IO), y si queremos trabajar con el valor anunciado, tenemos pocas opciones, pero trabajar con su interfaz monádica.
En el contexto de Scala, encontrará que la siguiente es la definición más simple. Básicamente flatMap (o bind) es ''asociativo'' y existe una identidad.
trait M[+A] {
def flatMap[B](f: A => M[B]): M[B] // AKA bind
// Pseudo Meta Code
def isValidMonad: Boolean = {
// for every parameter the following holds
def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))
// for every parameter X and x, there exists an id
// such that the following holds
def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
x.flatMap(id) == x
}
}
P.ej
// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)
// Observe these are identical. Since Option is a Monad
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)
scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)
// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)
// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)
scala> Some(7)
res214: Some[Int] = Some(7)
NOTA Estrictamente hablando, la definición de una mónada en la programación funcional no es la misma que la definición de una mónada en la teoría de categorías , que se define en turnos de map
y flatten
. Aunque son una especie de equivalente bajo ciertas asignaciones. Esta presentación es muy buena: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category
He estado pensando en las mónadas de una manera diferente, últimamente. He estado pensando en ellos como abstrayendo el orden de ejecución de una manera matemática, lo que hace posible nuevos tipos de polimorfismo.
Si está utilizando un lenguaje imperativo y escribe algunas expresiones en orden, el código SIEMPRE se ejecuta exactamente en ese orden.
Y en el caso simple, cuando usa una mónada, se siente igual: define una lista de expresiones que suceden en orden. Excepto que, dependiendo de la mónada que use, su código podría ejecutarse en orden (como en la mónada IO), en paralelo sobre varios elementos a la vez (como en la mónada Lista), podría detenerse a la mitad (como en la mónada Quizás) , podría detenerse a medio camino para reanudarse más tarde (como en una mónada Reanudación), podría rebobinarse y comenzar desde el principio (como en una mónada de Transacción), o podría rebobinarse parcialmente para probar otras opciones (como en una mónada Lógica) .
Y debido a que las mónadas son polimórficas, es posible ejecutar el mismo código en diferentes mónadas, según sus necesidades.
Además, en algunos casos, es posible combinar mónadas (con transformadores de mónada) para obtener múltiples funciones al mismo tiempo.
Monoid parece ser algo que garantiza que todas las operaciones definidas en un Monoid y un tipo compatible siempre devolverán un tipo compatible dentro del Monoid. Por ejemplo, Cualquier número + Cualquier número = Un número, sin errores.
Mientras que la división acepta dos fraccionarios, y devuelve un fraccionario, que definió la división por cero como Infinito en haskell somewhy (que resulta ser un fraccional somewhy) ...
En cualquier caso, parece que las mónadas son solo una forma de garantizar que su cadena de operaciones se comporte de una manera predecible, y una función que dice ser Num -> Num, compuesta con otra función de Num-> Num llamada con x no digamos, dispara los misiles.
Por otro lado, si tenemos una función que dispara los misiles, podemos componerla con otras funciones que también disparan los misiles, porque nuestra intención es clara: queremos disparar los misiles, pero no lo intentaremos imprimiendo "Hello World" por alguna extraña razón.
En Haskell, main es de tipo IO (), o IO [()], la discreción es extraña y no lo discutiré, pero esto es lo que creo que sucede:
Si tengo main, quiero que haga una cadena de acciones, la razón por la que ejecuto el programa es para producir un efecto, generalmente a través de IO. Por lo tanto, puedo encadenar las operaciones de IO en main para - hacer IO, nada más.
Si trato de hacer algo que no "devuelve IO", el programa se quejará de que la cadena no fluye, o básicamente "¿Cómo se relaciona esto con lo que estamos tratando de hacer? Una acción de IO", parece forzar el programador debe mantener su línea de pensamiento, sin desviarse y sin pensar en disparar los misiles, mientras crea algoritmos para la clasificación, que no fluye.
Básicamente, las mónadas parecen ser una sugerencia para el compilador que "oye, conoces esta función que devuelve un número aquí, en realidad no siempre funciona, a veces puede producir un número, y a veces nada en absoluto, solo mantén esto en mente". Sabiendo esto, si intentas afirmar una acción monádica, la acción monádica puede actuar como una excepción de tiempo de compilación que dice "oye, esto no es realmente un número, PUEDE ser un número, pero no puedes asumir esto, haz algo para garantizar que el flujo sea aceptable ". lo que evita un comportamiento impredecible del programa, en buena medida.
Parece que las mónadas no tienen que ver con la pureza, ni con el control, sino con el mantenimiento de una identidad de una categoría en la que todo comportamiento es predecible y definido, o no se compila. No puede hacer nada cuando se espera que haga algo, y no puede hacer algo si se espera que no haga nada (visible).
La razón más importante por la que puedo pensar para Monads es: ve a ver el código de procedimiento / OOP, y notarás que no sabes dónde comienza o termina el programa, todo lo que ves es un montón de saltos y muchas matemáticas , magia y misiles. No podrá mantenerlo, y si puede, pasará bastante tiempo concentrando su mente en todo el programa antes de que pueda comprender cualquier parte del mismo, porque la modularidad en este contexto se basa en "secciones" interdependientes de código, donde el código está optimizado para estar lo más relacionado posible para la promesa de eficiencia / interrelación. Las mónadas son muy concretas y están bien definidas por definición, y aseguran que el flujo del programa sea posible de analizar y aislar partes que son difíciles de analizar, ya que ellas mismas son mónadas. Una mónada parece ser un "unidad comprensible que es predecible a partir de su plena comprensión "- Si comprende la mónada" Quizás ", no hay forma posible de que haga nada excepto ser" Quizás ", que parece trivial, pero en la mayoría de los códigos no monádicos, una función simple" helloworld "puede disparar los misiles, no hacer nada, o destruir el universo o incluso distorsionar el tiempo. No tenemos ni idea ni garantías de que ES LO QUE ES. Una mónada GARANTIZA que ES LO QUE ES. lo cual es muy poderoso".o destruir el universo o incluso distorsionar el tiempo; no tenemos ni idea ni garantías de que ES LO QUE ES. Una mónada GARANTIZA QUE ES LO QUE ES. que es muy poderosoo destruir el universo o incluso distorsionar el tiempo; no tenemos ni idea ni garantías de que ES LO QUE ES. Una mónada GARANTIZA QUE ES LO QUE ES. que es muy poderoso
Todas las cosas en el "mundo real" parecen ser mónadas, en el sentido de que está sujeto a leyes observables definidas que evitan la confusión. Esto no significa que tengamos que imitar todas las operaciones de este objeto para crear clases, sino que simplemente podemos decir "un cuadrado es un cuadrado", nada más que un cuadrado, ni siquiera un rectángulo ni un círculo, y "un cuadrado tiene área de la longitud de una de sus dimensiones existentes multiplicada por sí misma. No importa qué cuadrado tenga, si es un cuadrado en el espacio 2D, su área no puede ser más que su longitud al cuadrado, es casi trivial probarlo. Esto es muy poderoso porque no necesitamos hacer afirmaciones para asegurarnos de que nuestro mundo sea como es, solo usamos las implicaciones de la realidad para evitar que nuestros programas se salgan del camino.
Estoy bastante seguro de estar equivocado, pero creo que esto podría ayudar a alguien, así que espero que ayude a alguien.
Si he entendido correctamente, IEnumerable se deriva de mónadas. Me pregunto si ese podría ser un ángulo de enfoque interesante para aquellos de nosotros del mundo de C #.
Para lo que vale, aquí hay algunos enlaces a tutoriales que me ayudaron (y no, todavía no entiendo qué son las mónadas).
Todavía soy nuevo en las mónadas, pero pensé que compartiría un enlace que encontré que se sintió realmente bien para leer (¡CON FOTOS!): http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/ (sin afiliación)
Básicamente, el concepto cálido y difuso que obtuve del artículo fue el concepto de que las mónadas son básicamente adaptadores que permiten que funciones dispares funcionen de una manera composable, es decir, ser capaz de agrupar múltiples funciones y mezclarlas y combinarlas sin preocuparse por un retorno inconsistente tipos y tal. Entonces, la función BIND se encarga de mantener las manzanas con manzanas y las naranjas con naranjas cuando intentamos hacer estos adaptadores. Y la función LIFT se encarga de tomar las funciones de "nivel inferior" y "actualizarlas" para que funcionen con las funciones BIND y también sean componibles.
Espero haberlo entendido bien y, lo que es más importante, espero que el artículo tenga una visión válida sobre las mónadas. Por lo menos, este artículo me ayudó a despertar mi apetito por aprender más sobre las mónadas.
Trataré de explicar Monad
en el contexto de Haskell.
En la programación funcional, la composición de funciones es importante. Permite que nuestro programa consista en funciones pequeñas y fáciles de leer.
Digamos que tenemos dos funciones: g :: Int -> String
y f :: String -> Bool
.
Podemos hacer (f . g) x
, que es lo mismo que f (gx)
, donde x
hay un Int
valor.
Al hacer la composición / aplicar el resultado de una función a otra, es importante que los tipos coincidan. En el caso anterior, el tipo del resultado devuelto por g
debe ser el mismo que el tipo aceptado por f
.
Pero a veces los valores están en contextos, y esto hace que sea un poco menos fácil alinear tipos. (Tener valores en contextos es muy útil. Por ejemplo, el Maybe Int
tipo representa un Int
valor que puede no estar allí, el IO String
tipo representa un String
valor que está allí como resultado de realizar algunos efectos secundarios).
Digamos que ahora tenemos g1 :: Int -> Maybe String
y f1 :: String -> Maybe Bool
. g1
y f1
son muy similares a g
y f
respectivamente.
No podemos hacer (f1 . g1) x
o f1 (g1 x)
, donde x
hay un Int
valor. El tipo de resultado devuelto por g1
no es el que se f1
espera.
Podríamos componer f
y g
con el .
operador, pero ahora no podemos componer f1
y g1
con.
. El problema es que no podemos pasar directamente un valor en un contexto a una función que espera un valor que no está en un contexto.
¿No sería bueno si presentamos un operador para componer g1
y f1
, de modo que podamos escribir (f1 OPERATOR g1) x
? g1
devuelve un valor en un contexto. El valor se sacará de contexto y se aplicará a f1
. Y sí, tenemos un operador así. Es <=<
.
También tenemos el >>=
operador que hace por nosotros exactamente lo mismo, aunque en una sintaxis ligeramente diferente.
Escribimos: g1 x >>= f1
. g1 x
es un Maybe Int
valor El >>=
operador ayuda a sacar ese Int
valor del contexto "quizás no esté" y aplicarlo f1
. El resultado de f1
, que es a Maybe Bool
, será el resultado de toda la >>=
operación.
Y finalmente, ¿por qué es Monad
útil? Porque Monad
es la clase de tipo que define el >>=
operador, muy similar a la Eq
clase de tipo que define los operadores ==
y /=
.
Para concluir, la Monad
clase de tipo define el >>=
operador que nos permite pasar valores en un contexto (los denominamos valores monádicos) a funciones que no esperan valores en un contexto. El contexto será atendido.
Si hay algo que recordar aquí, es que Monad
permite la composición de funciones que involucra valores en contextos .
Una mónada es una cosa utilizada para encapsular objetos que tienen un estado cambiante. Se encuentra con mayor frecuencia en idiomas que, de lo contrario, no le permiten tener un estado modificable (por ejemplo, Haskell).
Un ejemplo sería para el archivo de E / S.
Podrías usar una mónada para E / S de archivo para aislar la naturaleza de estado cambiante solo al código que usó la mónada. El código dentro de la mónada puede ignorar efectivamente el estado cambiante del mundo fuera de la mónada; esto hace que sea mucho más fácil razonar sobre el efecto general de su programa.
Una mónada es una forma de combinar cálculos que comparten un contexto común. Es como construir una red de tuberías. Al construir la red, no fluyen datos a través de ella. Pero cuando termine de unir todos los bits con ''bind'' y ''return'', invoco algo así runMyMonad monad data
y los datos fluyen a través de las tuberías.
Una mónada es, efectivamente, una forma de "operador de tipo". Hará tres cosas. Primero "envolverá" (o convertirá) un valor de un tipo en otro tipo (típicamente llamado "tipo monádico"). En segundo lugar, hará que todas las operaciones (o funciones) estén disponibles en el tipo subyacente disponible en el tipo monádico. Finalmente proporcionará soporte para combinarse con otra mónada para producir una mónada compuesta.
La "quizás mónada" es esencialmente el equivalente de "tipos anulables" en Visual Basic / C #. Toma un tipo "T" no anulable y lo convierte en un "Nullable <T>", y luego define lo que significan todos los operadores binarios en un Nullable <T>.
Los efectos secundarios se representan de manera similar. Se crea una estructura que contiene descripciones de los efectos secundarios junto con el valor de retorno de una función. Las operaciones "levantadas" luego copian los efectos secundarios a medida que se pasan valores entre funciones.
Se llaman "mónadas" en lugar del nombre más fácil de entender de "operadores de tipo" por varias razones:
- Las mónadas tienen restricciones sobre lo que pueden hacer (ver la definición para más detalles).
- Esas restricciones, junto con el hecho de que hay tres operaciones involucradas, se ajustan a la estructura de algo llamado mónada en la teoría de la categoría, que es una rama oscura de las matemáticas.
- Fueron diseñados por defensores de lenguajes funcionales "puros"
- Los defensores de lenguajes funcionales puros como ramas oscuras de las matemáticas.
- Debido a que las matemáticas son oscuras y las mónadas están asociadas con estilos particulares de programación, las personas tienden a usar la palabra mónada como una especie de apretón de manos secreto. Debido a esto, nadie se ha molestado en invertir en un mejor nombre.