Una mónada es solo un monoide en la categoría de endofunctores, ¿cuál es el problema?

723

¿Quién dijo primero lo siguiente?

Una mónada es solo un monoide en la categoría de endofunctores, ¿cuál es el problema?

Y en una nota menos importante, ¿es esto cierto y, de ser así, podría dar una explicación (con suerte, una que pueda entender alguien que no tenga mucha experiencia con Haskell)?

Roman A. Taycher
fuente
14
Ver "Categorías para el matemático que trabaja"
Don Stewart
19
No necesitas entender esto para usar mónadas en Haskell. Desde una perspectiva práctica, son solo una forma inteligente de pasar el "estado" a través de algunas tuberías subterráneas.
Starblue
1
Me gustaría agregar esta excelente publicación de blog aquí también: stephendiehl.com/posts/monads.html No responde directamente a la pregunta, pero en mi opinión Stephen hace un excelente trabajo al vincular categorías y mónadas en Haskell. Si ha leído las respuestas anteriores, esto debería ayudar a unificar las dos formas de ver esto.
Ben Ford
3
Más precisamente "Para cualquier categoría C, la categoría [C, C] de sus endofunctores tiene una estructura monoidal inducida por la composición. Un objeto monoide en [C, C] es una mónada en C." - de en.wikipedia.org/wiki/Monoid_%28category_theory%29. Ver en.wikipedia.org/wiki/Monad_%28category_theory%29 para la definición de mónada en la teoría de categorías.
1
@Dmitry Un functor es una función entre categorías, con algunas restricciones para tener un buen comportamiento. Un endofunctor en una categoría C es solo un functor de C en sí mismo. Data.Functor es una clase de tipo para endofunctors en la categoría Hask . Como una categoría consiste en objetos y morfismos, un functor necesita mapear ambos. Para una instancia f de Data.Functor, el mapa de objetos (tipos haskell) es f y el mapa de morfismos (funciones haskell) es fmap.
Matthijs

Respuestas:

796

Esa frase en particular es de James Iry, de su muy entretenida historia breve, incompleta y mayormente incorrecta de lenguajes de programación , en la que lo atribuye ficticiamente a Philip Wadler.

La cita original es de Saunders Mac Lane en Categorías para el matemático de trabajo , uno de los textos fundamentales de la teoría de categorías. Aquí está en contexto , que es probablemente el mejor lugar para aprender exactamente lo que significa.

Pero, voy a apuñalar. La oración original es esta:

En total, una mónada en X es solo un monoide en la categoría de endofunctores de X, con el producto × reemplazado por la composición de endofunctores y la unidad establecida por el endofunctor de identidad.

X aquí es una categoría. Los endofunctores son functores de una categoría en sí misma (que generalmente es todo Functor en lo que respecta a los programadores funcionales, ya que en su mayoría se trata de una sola categoría; la categoría de tipos, pero estoy divagando). Pero podrías imaginar otra categoría que es la categoría de "endofunctores en X ". Esta es una categoría en la que los objetos son endofunctores y los morfismos son transformaciones naturales.

Y de esos endofunctores, algunos de ellos podrían ser mónadas. ¿Cuáles son las mónadas? Exactamente los que son monoidales en un sentido particular. En lugar de detallar el mapeo exacto de mónadas a monoides (ya que Mac Lane lo hace mucho mejor de lo que podría esperar), simplemente pondré sus respectivas definiciones una al lado de la otra y te dejaré comparar:

Un monoide es ...

  • Un conjunto, S
  • Una operación, •: S × S → S
  • Un elemento de S , e: 1 → S

... cumpliendo estas leyes:

  • (a • b) • c = a • (b • c) , por todo un , b y c en S
  • e • a = a • e = a , para todo a en S

Una mónada es ...

  • Un endofunctor, T: X → X (en Haskell, un constructor de tipo de tipo * -> *con una Functorinstancia)
  • Una transformación natural, μ: T × T → T , donde × significa composición del functor ( μ se conoce como joinen Haskell)
  • Una transformación natural, η: I → T , donde I es el endofunctor de identidad en X ( η se conoce como returnen Haskell)

... cumpliendo estas leyes:

  • μ ∘ Tμ = μ ∘ μT
  • μ ∘ Tη = μ ∘ ηT = 1 (la transformación natural de identidad)

Al entrecerrar los ojos, puede ver que ambas definiciones son instancias del mismo concepto abstracto .

Tom Crockett
fuente
21
gracias por la explicación y gracias por el breve, incompleto y sobre todo incorrecto artículo de historia de lenguajes de programación. Pensé que podría ser a partir de ahí. Verdaderamente una de las mejores piezas de humor de programación.
Roman A. Taycher
66
@ Jonathan: En la formulación clásica de un monoide, × significa el producto cartesiano de los conjuntos. Puede leer más sobre esto aquí: en.wikipedia.org/wiki/Cartesian_product , pero la idea básica es que un elemento de S x T es un par (s, t) , donde s ∈ S y T ∈ T . Entonces, la firma del producto monoidal •: S × S -> S en este contexto simplemente significa una función que toma 2 elementos de S como entrada y produce otro elemento de S como salida.
Tom Crockett
12
@TahirHassan - En la generalidad de la teoría de categorías, tratamos con "objetos" opacos en lugar de conjuntos, por lo que no existe una noción a priori de "elementos". Pero si piensa en la categoría Conjunto donde los objetos son conjuntos y las flechas son funciones, los elementos de cualquier conjunto S están en correspondencia uno a uno con las funciones de cualquier conjunto de un elemento a S. Es decir, para cualquier elemento e de S , hay exactamente una función f: 1 -> S , donde 1 es cualquier conjunto de un elemento ... (continuación)
Tom Crockett
12
Los conjuntos de 1 elemento de @TahirHassan son en sí mismos especializaciones de la noción teórica de categoría más general de "objetos terminales": un objeto terminal es cualquier objeto de una categoría para la cual hay exactamente una flecha de cualquier otro objeto (puede verificar que Esto es cierto para los conjuntos de 1 elemento en Set ). En la teoría de categorías, los objetos terminales se denominan simplemente 1 ; son únicos hasta el isomorfismo, por lo que no tiene sentido distinguirlos. Así que ahora tenemos una descripción puramente teórica de categoría de "elementos de S " para cualquier S : ¡son solo las flechas del 1 al S !
Tom Crockett el
77
@TahirHassan: para poner esto en términos de Haskell, piense en el hecho de que si Ses un tipo, todo lo que puede hacer al escribir una función f :: () -> Ses elegir un término particular de tipo S(un "elemento" de la misma, si lo desea) y regresar ... no se le ha dado información real con el argumento, por lo que no hay forma de variar el comportamiento de la función. Por flo tanto, debe ser una función constante que simplemente devuelva lo mismo cada vez. ()("Unidad") es el objeto terminal de la categoría Hask , y no es coincidencia que haya exactamente un valor (no divergente) que lo habita.
Tom Crockett el
532

Intuitivamente, creo que lo que dice el elegante vocabulario matemático es que:

Monoide

Un monoide es un conjunto de objetos y un método para combinarlos. Los monoides bien conocidos son:

  • números que puedes agregar
  • listas que puedes concatenar
  • establece usted puede unión

Hay ejemplos más complejos también.

Además, cada monoide tiene una identidad , que es ese elemento "no operativo" que no tiene efecto cuando se combina con algo más:

  • 0 + 7 == 7 + 0 == 7
  • [] ++ [1,2,3] == [1,2,3] ++ [] == [1,2,3]
  • {} union {apple} == {apple} union {} == {apple}

Finalmente, un monoide debe ser asociativo . (puede reducir una larga cadena de combinaciones de la forma que desee, siempre que no cambie el orden de izquierda a derecha de los objetos) La adición está bien ((5 + 3) +1 == 5+ (3+ 1)), pero la resta no es ((5-3) -1! = 5- (3-1)).

Monada

Ahora, consideremos un tipo especial de conjunto y una forma especial de combinar objetos.

Objetos

Supongamos que su conjunto contiene objetos de un tipo especial: funciones . Y estas funciones tienen una firma interesante: no llevan números a números o cadenas a cadenas. En cambio, cada función lleva un número a una lista de números en un proceso de dos pasos.

  1. Calcule 0 o más resultados
  2. Combine esos resultados en una sola respuesta de alguna manera.

Ejemplos:

  • 1 -> [1] (solo ajusta la entrada)
  • 1 -> [] (descartar la entrada, envolver la nada en una lista)
  • 1 -> [2] (agregue 1 a la entrada y ajuste el resultado)
  • 3 -> [4, 6] (agregue 1 a la entrada, multiplique la entrada por 2 y ajuste el resultados múltiples )

Combinando objetos

Además, nuestra forma de combinar funciones es especial. Una forma sencilla de combinar la función es la composición. : tomemos nuestros ejemplos anteriores y compongamos cada función consigo misma:

  • 1 -> [1] -> [[1]] (ajusta la entrada, dos veces)
  • 1 -> [] -> [] (descartar la entrada, envolver la nada en una lista, dos veces)
  • 1 -> [2] -> [UH-OH! ] (¡no podemos "agregar 1" a una lista! ")
  • 3 -> [4, 6] -> [UH-OH! ] (¡no podemos agregar 1 a la lista!)

Sin entrar demasiado en la teoría de tipos, el punto es que puedes combinar dos enteros para obtener un entero, pero no siempre puedes componer dos funciones y obtener una función del mismo tipo. (Las funciones con tipo a -> a compondrán, pero a-> [a] no).

Entonces, definamos una forma diferente de combinar funciones. Cuando combinamos dos de estas funciones, no queremos "ajustar dos veces" los resultados.

Esto es lo que hacemos. Cuando queremos combinar dos funciones F y G, seguimos este proceso (llamado enlace ):

  1. Calcule los "resultados" de F pero no los combine.
  2. Calcule los resultados de la aplicación de G a cada uno de los resultados de F por separado, produciendo una colección de colección de resultados.
  3. Aplane la colección de 2 niveles y combine todos los resultados.

Volviendo a nuestros ejemplos, combinemos (vinculemos) una función consigo misma usando esta nueva forma de "vincular" funciones:

  • 1 -> [1] -> [1] (ajusta la entrada, dos veces)
  • 1 -> [] -> [] (descartar la entrada, envolver la nada en una lista, dos veces)
  • 1 -> [2] -> [3] (agregue 1, luego agregue 1 nuevamente y ajuste el resultado).
  • 3 -> [4,6] -> [5,8,7,12] (agregue 1 a la entrada, y también multiplique la entrada por 2, manteniendo ambos resultados, luego vuelva a hacerlo en ambos resultados y luego envuelva el resultado final resultados en una lista.)

Esta forma más sofisticada de combinar funciones es asociativa (a partir de cómo la composición de funciones es asociativa cuando no está haciendo el envoltorio elegante).

Atarlo todo junto

  • una mónada es una estructura que define una forma de combinar (los resultados de) funciones,
  • de manera análoga a cómo un monoide es una estructura que define una forma de combinar objetos,
  • donde el método de combinación es asociativo,
  • y donde hay un 'No-op' especial que se puede combinar con cualquier cosa para obtener algo sin cambios.

Notas

Hay muchas formas de "ajustar" los resultados. Puede hacer una lista, o un conjunto, o descartar todos menos el primer resultado mientras observa si no hay resultados, adjunte un sidecar de estado, imprima un mensaje de registro, etc., etc.

He jugado un poco con las definiciones con la esperanza de transmitir la idea esencial de forma intuitiva.

He simplificado un poco las cosas al insistir en que nuestra mónada opera en funciones de tipo a -> [a] . De hecho, las mónadas trabajan en funciones de tipo a -> mb , pero la generalización es una especie de detalle técnico que no es la idea principal.

misterbee
fuente
22
Esta es una buena explicación de cómo cada mónada constituye una categoría (la categoría Kleisli es lo que está demostrando; también está la categoría Eilenberg-Moore). Sin embargo, debido al hecho de que no se puede componer cualquier dos flechas Kleisli a -> [b]y c -> [d](sólo se puede hacer esto si b= c), esto no acaba de describir un monoide. En realidad, es la operación de aplanamiento que describió, en lugar de la composición de la función, que es el "operador monoide".
Tom Crockett
66
De acuerdo, si limitara una mónada a un solo tipo, es decir, si solo permitiera flechas Kleisli de la forma a -> [a], esto sería un monoide (porque estaría reduciendo la categoría Kleisli a un solo objeto, y cualquier categoría de un solo objeto ¡es por definición un monoide!), pero no capturaría la generalidad completa de la mónada.
Tom Crockett
55
En la última nota, ayuda recordar que a -> [a] es solo un -> [] a. ([] es solo un constructor de tipos también.) Por lo tanto, no solo puede verse como un -> mb, sino que [] es, de hecho, una instancia de la clase Monad.
Evi1M4chine
8
Esta es la mejor y más sorprendente explicación de las mónadas y sus antecedentes matemáticos de monoides que he encontrado literalmente en semanas. Esto es lo que debe imprimirse en cada libro de Haskell cuando se trata de mónadas, sin duda. ¡VOTAR! Tal vez obtenga más información, que las mónadas se realizan como instancias de clases de tipos parametrizadas que envuelven lo que se haya puesto en ellas en la publicación. (Al menos así es como los entendí ahora. Corrígeme si me equivoco. Ver haskell.org/haskellwiki/What_a_Monad_is_not )
sjas
1
Esto es fantástico: es la única explicación que he entendido lo suficientemente bien como para poder explicárselo a otra persona ... Pero aún no entiendo por qué esta es una forma valiosa de pensar en algo. :(
Adam Barnes
84

Primero, las extensiones y bibliotecas que vamos a usar:

{-# LANGUAGE RankNTypes, TypeOperators #-}

import Control.Monad (join)

De estos, RankNTypeses el único que es absolutamente esencial para lo siguiente. Una vez escribí una explicación de RankNTypesque algunas personas parecen haber encontrado útil , así que me referiré a eso.

Citando la excelente respuesta de Tom Crockett , tenemos:

Una mónada es ...

  • Un endofunctor, T: X -> X
  • Una transformación natural, μ: T × T -> T , donde × significa composición del functor
  • Una transformación natural, η: I -> T , donde I es el endofunctor de identidad en X

... cumpliendo estas leyes:

  • μ (μ (T × T) × T)) = μ (T × μ (T × T))
  • μ (η (T)) = T = μ (T (η))

¿Cómo traducimos esto al código Haskell? Bueno, comencemos con la noción de una transformación natural :

-- | A natural transformations between two 'Functor' instances.  Law:
--
-- > fmap f . eta g == eta g . fmap f
--
-- Neat fact: the type system actually guarantees this law.
--
newtype f :-> g =
    Natural { eta :: forall x. f x -> g x }

Un tipo de formulario f :-> ges análogo a un tipo de función, pero en lugar de pensarlo como una función entre dos tipos (de tipo *), piense en él como un morfismo entre dos functores (cada uno de tipo * -> *). Ejemplos:

listToMaybe :: [] :-> Maybe
listToMaybe = Natural go
    where go [] = Nothing
          go (x:_) = Just x

maybeToList :: Maybe :-> []
maybeToList = Natural go
    where go Nothing = []
          go (Just x) = [x]

reverse' :: [] :-> []
reverse' = Natural reverse

Básicamente, en Haskell, las transformaciones naturales son funciones de un tipo f xa otro, de g xmodo que la xvariable de tipo es "inaccesible" para la persona que llama. Entonces, por ejemplo, sort :: Ord a => [a] -> [a]no se puede convertir en una transformación natural, porque es "exigente" sobre qué tipos podemos crear instancias a. Una forma intuitiva que suelo usar para pensar en esto es la siguiente:

  • Un functor es una forma de operar sobre el contenido de algo sin tocar la estructura .
  • Una transformación natural es una forma de operar en el estructura de algo sin tocar ni mirar el contenido .

Ahora, con eso fuera del camino, abordemos las cláusulas de la definición.

La primera cláusula es "un endofunctor, T: X -> X ". Bueno, todo Functoren Haskell es un endofunctor en lo que la gente llama "la categoría Hask", cuyos objetos son tipos Haskell (de tipo *) y cuyos morfismos son funciones Haskell. Esto suena como una declaración complicada, pero en realidad es muy trivial. Todo lo que significa es que unFunctor f :: * -> * le brinda los medios para construir un tipo f a :: *para cualquiera a :: *y una función a fmap f :: f a -> f bpartir de cualquiera f :: a -> b, y que estos obedecen las leyes de los functores.

Segunda cláusula: el Identity functor en Haskell (que viene con la Plataforma, por lo que puede importarlo) se define de esta manera:

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    fmap f (Identity a) = Identity (f a)

Entonces la transformación natural η: I -> T de la definición de Tom Crockett se puede escribir de esta manera para cualquierMonad caso t:

return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)

Tercera cláusula: la composición de dos functores en Haskell se puede definir de esta manera (que también viene con la Plataforma):

newtype Compose f g a = Compose { getCompose :: f (g a) }

-- | The composition of two 'Functor's is also a 'Functor'.
instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose fga) = Compose (fmap (fmap f) fga)

Entonces la transformación natural μ: T × T -> T de la definición de Tom Crockett se puede escribir así:

join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)

La afirmación de que este es un monoide en la categoría de endofunctores significa que Compose(aplicado parcialmente a sus dos primeros parámetros) es asociativo, y ese Identityes su elemento de identidad. Es decir, que tienen los siguientes isomorfismos:

  • Compose f (Compose g h) ~= Compose (Compose f g) h
  • Compose f Identity ~= f
  • Compose Identity g ~= g

Estos son muy fáciles de probar porque Composey Identityambos se definen como newtype, y los Informes Haskell definen la semántica de newtypecomo un isomorfismo entre el tipo que se está definiendo y el tipo de argumento para el newtypeconstructor de datos. Entonces, por ejemplo, demostremos Compose f Identity ~= f:

Compose f Identity a
    ~= f (Identity a)                 -- newtype Compose f g a = Compose (f (g a))
    ~= f a                            -- newtype Identity a = Identity a
Q.E.D.
Luis Casillas
fuente
En el Naturalnuevo tipo, no puedo entender qué (Functor f, Functor g)está haciendo la restricción. ¿Podrías explicar?
dfeuer
@dfeuer Realmente no está haciendo nada esencial.
Luis Casillas
1
@LuisCasillas He eliminado esas Functorrestricciones ya que no parecen necesarias. Si no está de acuerdo, no dude en agregarlos nuevamente.
Lambda Fairy
¿Puedes dar más detalles sobre lo que significa formalmente que el producto de los functores se tome como composición? En particular, ¿cuáles son los morfismos de proyección para la composición del functor? Supongo que el producto solo está definido para un functor F contra sí mismo, F x F y solo cuando joinestá definido. Y esa joines la proyección del morfismo. Pero no estoy seguro.
tksfz 01 de
6

Nota: No, esto no es cierto. En algún momento hubo un comentario sobre esta respuesta del propio Dan Piponi que decía que la causa y el efecto aquí era exactamente lo contrario, que escribió su artículo en respuesta al comentario de James Iry. Pero parece haber sido eliminado, quizás por algún tidier compulsivo.

A continuación se muestra mi respuesta original.


Es muy posible que Iry haya leído From Monoids to Monads , una publicación en la que Dan Piponi (sigfpe) deriva mónadas de monoids en Haskell, con mucha discusión sobre la teoría de categorías y mención explícita de "la categoría de endofunctores en Hask ". En cualquier caso, cualquiera que se pregunte qué significa para una mónada ser un monoide en la categoría de endofunctores podría beneficiarse al leer esta derivación.

hobbs
fuente
1
"Quizás por algún tidier compulsivo" - o, como nos referimos a ellos con cariño en este sitio, un moderador :-).
halfer
6

Llegué a esta publicación para comprender mejor la inferencia de la cita infame de la teoría de la categoría de Mac Lane para el matemático que trabaja .

Al describir qué es algo, a menudo es igualmente útil describir lo que no es.

El hecho de que Mac Lane use la descripción para describir una mónada, podría implicar que describe algo único de las mónadas. Tengan paciencia conmigo. Para desarrollar una comprensión más amplia de la declaración, creo que es necesario aclarar que él no describiendo algo que es único para mónadas; la declaración describe igualmente Aplicativo y Flechas entre otros. Por la misma razón, podemos tener dos monoides en Int (Sum y Product), podemos tener varios monoides en X en la categoría de endofunctores. Pero hay aún más en las similitudes.

Tanto Monad como aplicativo cumplen los criterios:

  • endo => cualquier flecha o morfismo que comienza y termina en el mismo lugar
  • functor => cualquier flecha o morfismo entre dos categorías

    (por ejemplo, en el día a día Tree a -> List b, pero en la categoría Tree -> List)

  • monoide => objeto único; es decir, un solo tipo, pero en este contexto, solo en lo que respecta a la capa externa; entonces, no podemos tener Tree -> List, solo List -> List.

La declaración usa "Categoría de ..." Esto define el alcance de la declaración. Como ejemplo, la categoría Functor describe el alcance de f * -> g *, es decir Any functor -> Any functor, por ejemplo, Tree * -> List *oTree * -> Tree * .

Lo que una declaración categórica no especifica describe dónde se permite cualquier cosa y todo .

En este caso, dentro de los functores, * -> *aka a -> bno se especifica qué significa Anything -> Anything including Anything else. A medida que mi imaginación salta a Int -> String, también incluye Integer -> Maybe Int, o incluso Maybe Double -> Either String Intdónde a :: Maybe Double; b :: Either String Int.

Entonces la declaración se une de la siguiente manera:

  • alcance functor :: f a -> g b (es decir, cualquier tipo parametrizado a cualquier tipo parametrizado)
  • endo + functor :: f a -> f b(es decir, cualquier tipo parametrizado al mismo tipo parametrizado) ... dicho de manera diferente,
  • un monoide en la categoría de endofunctor

Entonces, ¿dónde está el poder de esta construcción? Para apreciar la dinámica completa, necesitaba ver que los dibujos típicos de un monoide (objeto único con lo que parece una flecha de identidad :: single object -> single object) no logran ilustrar que se me permite usar una flecha parametrizada con cualquier número de valores de monoide, del uno objeto de tipo permitido en Monoid. La definición de equivalencia de la flecha endo, ~ ignora el valor de tipo del functor y tanto el tipo como el valor de la capa más interna de "carga útil". Por lo tanto, la equivalencia regresa trueen cualquier situación en la que coincidan los tipos de functorial (por ejemplo, Nothing -> Just * -> Nothinges equivalente a Just * -> Just * -> Just *porque son ambos Maybe -> Maybe -> Maybe).

Barra lateral: ~ afuera es conceptual, pero es el símbolo más a la izquierda adentro f a. También describe lo que "Haskell" lee primero (panorama general); por lo que Tipo está "afuera" en relación con un Valor de tipo. La relación entre capas (una cadena de referencias) en la programación no es fácil de relacionar en la Categoría. La Categoría de conjunto se usa para describir Tipos (Int, Strings, Quizás Int, etc.) que incluye la Categoría de Functor (Tipos parametrizados). La cadena de referencia: Tipo de Functor, valores de Functor (elementos del conjunto de ese Functor, por ejemplo, Nothing, Just) y, a su vez, todo lo demás a lo que apunta cada valor de functor. En la categoría, la relación se describe de manera diferente, por ejemplo, return :: a -> m ase considera una transformación natural de un Functor a otro Functor, diferente de todo lo mencionado hasta ahora.

Volviendo al hilo principal, en general, para cualquier producto tensor definido y un valor neutral, la declaración termina describiendo una construcción computacional increíblemente poderosa nacida de su estructura paradójica:

  • en el exterior aparece como un solo objeto (por ejemplo, :: List); estático
  • pero por dentro, permite mucha dinámica
    • cualquier cantidad de valores del mismo tipo (por ejemplo, Vacío | ~ No vacío) como forraje para funciones de cualquier aridad. El producto tensor reducirá cualquier cantidad de entradas a un solo valor ... para la capa externa (~ foldque no dice nada sobre la carga útil)
    • rango infinito tanto del tipo como de los valores para la capa más interna

En Haskell, es importante aclarar la aplicabilidad de la declaración. El poder y la versatilidad de esta construcción, no tiene absolutamente nada que ver con una mónada per se . En otras palabras, la construcción no se basa en lo que hace que una mónada sea única.

Cuando se trata de averiguar si se debe crear código con un contexto compartido para admitir cálculos que dependen unos de otros, frente a los cálculos que se pueden ejecutar en paralelo, esta declaración infame, con todo lo que describe, no es un contraste entre la elección de Aplicativo, flechas y mónadas, pero más bien es una descripción de cuánto son lo mismo. Para la decisión en cuestión, la declaración es discutible.

Esto a menudo se entiende mal. La declaración continúa describiéndose join :: m (m a) -> m acomo el producto tensor para el endofunctor monoidal. Sin embargo, no articula cómo, en el contexto de esta declaración, (<*>)también podría haber sido elegida. Realmente es un ejemplo de seis / media docena. La lógica para combinar valores es exactamente igual; la misma entrada genera la misma salida de cada uno (a diferencia de los monoides de Suma y Producto para Int porque generan resultados diferentes al combinar Ints).

Entonces, para recapitular: un monoide en la categoría de endofunctores describe:

   ~t :: m * -> m * -> m *
   and a neutral value for m *

(<*>)y (>>=)ambos proporcionan acceso simultáneo a los dos mvalores para calcular el valor de retorno único. La lógica utilizada para calcular el valor de retorno es exactamente la misma. Si no fuera por las diferentes formas de las funciones que parametrizan ( f :: a -> bversus k :: a -> m b) y la posición del parámetro con el mismo tipo de retorno del cálculo (es decir, a -> b -> bversus b -> a -> bpara cada uno respectivamente), sospecho que podríamos haber parametrizado la lógica monoidal, la producto tensor, para reutilizar en ambas definiciones. A modo de ejercicio para hacer el punto, tratar de poner en práctica ~t, y se termina con (<*>)y (>>=)dependiendo de lo que decida definirlo forall a b.

Si mi último punto es mínimo conceptualmente verdadero, entonces explica la diferencia computacional precisa y única entre Applicative y Monad: las funciones que parametrizan. En otras palabras, la diferencia es externa a la implementación de estas clases de tipos.

En conclusión, según mi propia experiencia, la cita infame de Mac Lane proporcionó un gran meme "goto", una guía a la que debo referirme mientras navego por la Categoría para comprender mejor los modismos utilizados en Haskell. Logra capturar el alcance de una poderosa capacidad de computación hecha maravillosamente accesible en Haskell.

Sin embargo, hay ironía en cómo primero entendí mal la aplicabilidad de la declaración fuera de la mónada, y lo que espero transmitió aquí. Todo lo que describe resulta ser similar entre Aplicativo y Mónadas (y Arrows entre otros). Lo que no dice es precisamente la pequeña pero útil distinción entre ellos.

- E

Eco de Edmund
fuente
5

Las respuestas aquí hacen un excelente trabajo al definir tanto monoides como mónadas, sin embargo, todavía no parecen responder la pregunta:

Y en una nota menos importante, ¿es esto cierto y, de ser así, podría dar una explicación (con suerte, una que pueda entender alguien que no tenga mucha experiencia con Haskell)?

El quid de la cuestión que falta aquí es la noción diferente de "monoide", la denominada categorización más precisamente: la de monoide en una categoría monoidal. Lamentablemente, el libro de Mac Lane lo hace muy confuso :

En total, una mónada Xes solo un monoide en la categoría de endofunctores de X, con el producto ×reemplazado por la composición de endofunctores y la unidad establecida por el endofunctor de identidad.

Confusión principal

¿Por qué es esto confuso? Porque no define qué es "monoide en la categoría de endofunctores" de X. En cambio, esta oración sugiere tomar un monoide dentro del conjunto de todos los endofunctores junto con la composición del functor como operación binaria y el functor de identidad como una unidad monoidal. Que funciona perfectamente bien y convierte en un monoide cualquier subconjunto de endofunctores que contiene el functor de identidad y está cerrado bajo la composición del functor.

Sin embargo, esta no es la interpretación correcta, que el libro no deja en claro en esa etapa. Una mónada fes un endofunctor fijo , no un subconjunto de endofunctores cerrados en composición. Una construcción común es usar fpara generar un monoide tomando el conjunto de kcomposiciones plegadas f^k = f(f(...))de fsí mismo, incluido el k=0que corresponde a la identidad f^0 = id. Y ahora el conjunto Sde todos estos poderes para todos k>=0es de hecho un monoide "con producto × reemplazado por composición de endofunctores y unidad establecida por el endofunctor de identidad".

Y todavía:

  • Este monoide Sse puede definir para cualquier functor fo incluso literalmente para cualquier auto-mapa de X. Es el monoide generado por f.
  • La estructura monoidal Sdada por la composición functor y el functor de identidad no tiene nada que ver con fser o no ser una mónada.

Y para hacer las cosas más confusas, la definición de "monoide en la categoría monoidal" aparece más adelante en el libro, como puede ver en la tabla de contenido . Y, sin embargo, comprender esta noción es absolutamente fundamental para comprender la conexión con las mónadas.

Categorías (estrictas) monoidales

Pasando al Capítulo VII sobre Monoides (que viene después del Capítulo VI sobre Mónadas), encontramos la definición de la llamada categoría monoidal estricta como triple (B, *, e), donde Bes una categoría, *: B x B-> Bun bifunctor (functor con respecto a cada componente con otro componente fijo ) y ees un objeto unitario que Bsatisface las leyes de asociatividad y unidad:

(a * b) * c = a * (b * c)
a * e = e * a = a

para cualquier objeto a,b,cde B, y las mismas identidades para cualquier morfismo a,b,ccon ereemplazado por id_e, el morfismo de identidad de e. Ahora es instructivo observar que en nuestro caso de interés, donde se Bencuentra la categoría de endofunctores Xcon transformaciones naturales como morfismos, *la composición del functor y eel functor de identidad, se cumplen todas estas leyes, como puede verificarse directamente.

Lo que viene después en el libro es la definición de la categoría monoidal "relajada" , donde las leyes solo contienen algunas transformaciones naturales fijas que satisfacen las llamadas relaciones de coherencia , lo que sin embargo no es importante para nuestros casos de las categorías de endofunctores.

Monoides en categorías monoidales

Finalmente, en la sección 3 "Monoides" del Capítulo VII, se da la definición real:

Un monoide cen una categoría monoidal (B, *, e)es un objeto Bcon dos flechas (morfismos)

mu: c * c -> c
nu: e -> c

Haciendo 3 diagramas conmutativos. Recordemos que en nuestro caso, estos son morfismos en la categoría de endofunctores, que son transformaciones naturales correspondientes a una mónada joiny precisamente return. La conexión se vuelve aún más clara cuando hacemos que la composición sea *más explícita, reemplazando c * cpor c^2dónde cestá nuestra mónada.

Finalmente, observe que los 3 diagramas conmutativos (en la definición de un monoide en la categoría monoidal) están escritos para categorías monoidales generales (no estrictas), mientras que en nuestro caso todas las transformaciones naturales que surgen como parte de la categoría monoidal son en realidad identidades. Eso hará que los diagramas sean exactamente iguales a los de la definición de una mónada, completando la correspondencia.

Conclusión

En resumen, cualquier mónada es, por definición, un endofunctor, por lo tanto, un objeto en la categoría de endofunctores, donde el monádico joiny los returnoperadores satisfacen la definición de un monoide en esa categoría monoidal particular (estricta) . Viceversa, cualquier monoide en la categoría monoidal de endofunctores es, por definición, un triple que (c, mu, nu)consiste en un objeto y dos flechas, por ejemplo, transformaciones naturales en nuestro caso, que satisfacen las mismas leyes que una mónada.

Finalmente, observe la diferencia clave entre los monoides (clásicos) y los monoides más generales en categorías monoidales. Las dos flechas muy nuarriba ya no son una operación binaria y una unidad en un conjunto. En cambio, tiene un endofunctor fijo c. La composición del functor *y el functor de identidad por sí solos no proporcionan la estructura completa necesaria para la mónada, a pesar de esa observación confusa en el libro.

Otro enfoque sería comparar con el monoide estándar Cde todos los auto-mapas de un conjunto A, donde la operación binaria es la composición, que se puede ver a mapear el producto cartesiano estándar C x Cen C. Pasando al monoide categorizado, estamos reemplazando el producto cartesiano xcon la composición del functor *, y la operación binaria se reemplaza con la transformación natural mude c * ca c, que es una colección de joinoperadores

join: c(c(T))->c(T)

para cada objeto T(escriba en la programación). Y los elementos de identidad en monoides clásicos, que pueden identificarse con imágenes de mapas de un conjunto fijo de un punto, se reemplazan con la colección de returnoperadores

return: T->c(T) 

Pero ahora no hay más productos cartesianos, por lo que no hay pares de elementos y, por lo tanto, no hay operaciones binarias.

Dmitri Zaitsev
fuente
Entonces, ¿cuál es su respuesta a la parte "es esto cierto" de la pregunta? ¿Es cierto que una mónada es un monoide en la categoría de endofunctores? Y en caso afirmativo, ¿cuál es la relación entre la noción de teoría de categoría de un monoide y un monoide algebraico (un conjunto con una multiplicación asociativa y una unidad)?
Alexander Belopolsky