haskell - Ejemplos simples para ilustrar Category, Monoid y Monad?
monads category-theory (1)
Me estoy confundiendo mucho con estos tres conceptos.
¿Hay algún ejemplo simple para ilustrar las diferencias entre Category, Monoid y Monad?
Sería muy útil si hay una ilustración de estos conceptos abstractos.
Probablemente esta no sea la respuesta que está buscando, pero de todos modos, aquí va:
Una forma realmente torcida de mirar las mónadas y la cooperación.
Una forma de ver conceptos abstractos como estos es vincularlos con conceptos básicos, como operaciones ordinarias de procesamiento de listas. Entonces, podrías decir eso,
- Una categoría generaliza la operación
(.)
. - Un monoide generaliza la operación
(++)
. - Un functor generaliza la operación del
map
. - Un functor aplicativo generaliza la operación
zip
(ozipWith
). - Una mónada generaliza la operación de
concat
.
Una categoría
Una categoría consiste en un conjunto (o una clase) de objetos y un grupo de flechas que conectan dos de los objetos. Además, para cada objeto, debería haber una flecha de identidad que conecta este objeto consigo mismo. Además, si hay una flecha ( f
) que termina en un objeto y otra ( g
) que parte del mismo objeto, entonces también debe haber una flecha compuesta llamada g . f
g . f
.
En Haskell esto se modela como una clase de tipo que representa la categoría de los tipos de Haskell como objetos.
class Category cat where
id :: cat a a
(.) :: cat b c -> cat a b -> cat a c
Los ejemplos básicos de una categoría son funciones. Cada función conecta dos tipos, para todos los tipos, existe la función id :: a -> a
que conecta el tipo (y el valor) consigo mismo. La composición de las funciones es la composición de la función ordinaria.
En resumen, las categorías en la base de Haskell son cosas que se comportan como funciones , es decir, puedes poner una tras otra con una versión generalizada de (.)
.
Un monoide
Un monoide es un conjunto con un elemento de unidad y una operación asociativa. Esto está modelado en Haskell como:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Ejemplos comunes de monoides incluyen:
- conjunto de enteros, el elemento 0 y la operación
(+)
. - conjunto de enteros positivos, el elemento 1 y la operación
(*)
. - conjunto de todas las listas, la lista vacía
[]
y la operación(++)
.
Estos están modelados en Haskell como
newtype Sum a = Sum {getSum :: a}
instance (Num a) => Monoid (Sum a) where
mempty = Sum 0
mappend (Sum a) (Sum b) = Sum (a + b)
instance Monoid [a] where
mempty = []
mappend = (++)
Los monoides se utilizan para "combinar" y acumular cosas. Por ejemplo, la función mconcat :: Monoid a => [a] -> a
, se puede usar para reducir una lista de sumas a una sola suma, o una lista anidada en una lista plana. Considere esto como una especie de generalización de operaciones (++)
o (+)
que de alguna manera "fusionan" dos cosas.
A Functor
Un functor en Haskell es una cosa que generaliza de manera bastante directa el map :: (a->b) -> [a] -> [b]
operaciones map :: (a->b) -> [a] -> [b]
. En lugar de mapear sobre una lista, se correlaciona con alguna estructura , como una lista, un árbol binario o incluso una operación IO. Los funtores se modelan así:
class Functor f where
fmap :: (a->b) -> f a -> f b
Contraste esto con la definición de la función de map
normal.
Un Funcionador Aplicativo
Los funtores aplicativos se pueden ver como cosas con una operación zipWith
general con zipWith
. Los funtores hacen un mapa de las estructuras generales una a la vez, pero con un funcionador Aplicativo puede unir dos o más estructuras. Para el ejemplo más simple, puede usar los aplicativos para comprimir dos enteros dentro del tipo Maybe
:
pure (+) <*> Just 1 <*> Just 2 -- gives Just 3
Tenga en cuenta que la estructura puede afectar el resultado, por ejemplo:
pure (+) <*> Nothing <*> Just 2 -- gives Nothing
Contraste esto a la función zipWith
habitual:
zipWith (+) [1] [2]
En lugar de solo listas, el aplicativo funciona para todo tipo de estructuras. Además, el truco ingenioso con pure
y (<*>)
generaliza la compresión para trabajar con cualquier cantidad de argumentos. Para ver cómo funciona esto, inspeccione los siguientes tipos mientras mantiene el concepto de funciones parcialmente aplicadas a mano:
instance (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Observe también la similitud entre fmap
y (<*>)
.
Una mónada
Las mónadas a menudo se utilizan para modelar contextos computacionales diferentes, como cálculos no deterministas o de efecto lateral. Como ya hay demasiados tutoriales de mónada, solo recomendaré el mejor , en lugar de escribir otro más.
En relación con las funciones de procesamiento de listas ordinarias, las mónadas generalizan la función concat :: [[a]] -> [a]
para trabajar con muchos otros tipos de estructuras además de listas. Como un ejemplo simple, la join
operación monádica se puede usar para aplanar los valores Maybe
anidados:
join (Just (Just 42)) -- gives Just 42
join (Just (Nothing)) -- gives Nothing
¿Cómo se relaciona esto con el uso de las Mónadas como medio para estructurar los cálculos? Considere un ejemplo de juguete donde hace dos consultas consecutivas desde alguna base de datos. La primera consulta le devuelve un valor clave, con el que desea hacer otra búsqueda. El problema aquí es que el primer valor está dentro de Maybe
, por lo que no puede consultarlo directamente. En cambio, como quizás sea un Functor
, en su lugar podría fmap
el valor de retorno con la nueva consulta. Esto le daría dos valores anidados de Maybe
como el anterior. Otra consulta daría como resultado tres capas de Maybe
s. Esto sería bastante difícil de programar, pero una join
monádica te ofrece una forma de aplanar esta estructura y trabajar con un solo nivel de Maybe
s.
(Creo que voy a editar mucho esta publicación antes de que tenga sentido ...)