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 f x
( 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 f g 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 c r
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 m b
.
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 -> m b
a los resultados de un cálculo m a
.
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 f g 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 f x
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`)
entonces
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 k l 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 estop
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 utiliza 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 el igualmente referencialmente transparente, 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 m f 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 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 qué 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
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, es porque 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 combinadoras. ¡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