haskell monoids

haskell - ¿Cuál es el uso práctico de los monoides?



monoids (4)

Estoy leyendo Learn You a Haskell y ya he cubierto el aplicativo y ahora estoy en monoides. No tengo problemas para entender los dos, aunque encontré que el aplicativo es útil en la práctica y el monoide no lo es tanto. Así que creo que no entiendo algo sobre Haskell.

En primer lugar, hablando de Applicative , crea una sintaxis uniforme para realizar varias acciones en "contenedores". Así que podemos usar funciones normales para realizar acciones en Maybe , listas, IO (¿debería haber dicho mónadas? Todavía no conozco las mónadas), funciones:

λ> :m + Control.Applicative λ> (+) <$> (Just 10) <*> (Just 13) Just 23 λ> (+) <$> [1..5] <*> [1..5] [2,3,4,5,6,3,4,5,6,7,4,5,6,7,8,5,6,7,8,9,6,7,8,9,10] λ> (++) <$> getLine <*> getLine one line and another one "one line and another one" λ> (+) <$> (* 7) <*> (+ 7) $ 10 87

Tan aplicativo es una abstracción. Creo que podemos vivir sin él, pero ayuda a expresar con claridad el modo de algunas ideas y eso está bien.

Ahora, echemos un vistazo a Monoid . También es abstracción y bastante simple. ¿Pero nos ayuda? Para cada ejemplo del libro, parece ser obvio que hay una manera más clara de hacer las cosas:

λ> :m + Data.Monoid λ> mempty :: [a] [] λ> [1..3] `mappend` [4..6] [1,2,3,4,5,6] λ> [1..3] ++ [4..6] [1,2,3,4,5,6] λ> mconcat [[1,2],[3,6],[9]] [1,2,3,6,9] λ> concat [[1,2],[3,6],[9]] [1,2,3,6,9] λ> getProduct $ Product 3 `mappend` Product 9 27 λ> 3 * 9 27 λ> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2 24 λ> product [3,4,2] 24 λ> getSum . mconcat . map Sum $ [1,2,3] 6 λ> sum [1..3] 6 λ> getAny . mconcat . map Any $ [False, False, False, True] True λ> or [False, False, False, True] True λ> getAll . mconcat . map All $ [True, True, True] True λ> and [True, True, True] True

Así que hemos notado algunos patrones y creado una nueva clase de tipos ... Bien, me gustan las matemáticas. Pero desde el punto de vista práctico, ¿cuál es el punto de Monoid ? ¿Cómo nos ayuda a expresar mejor las ideas?


Bien me gustan las matematicas Pero desde el punto de vista práctico, ¿cuál es el punto de Monoid? ¿Cómo nos ayuda a expresar mejor las ideas?

Es una API. Una simple. Para los tipos que soportan:

  • tener un elemento cero
  • tener una operación de adición

Muchos tipos soportan estas operaciones. Por lo tanto, tener un nombre para las operaciones y una API nos ayuda a capturar el hecho con mayor claridad.

Las API son buenas porque nos permiten reutilizar el código y reutilizar conceptos. Significado mejor, código más mantenible.


El punto es que cuando etiqueta un Int como Product , expresa su intención de multiplicar los enteros. Y etiquetándolos como Sum , para sumarlos.

Entonces puedes usar el mismo mconcat en ambos. Esto se usa, por ejemplo, en Foldable donde un foldMap expresa la idea de plegar una estructura contenedora, mientras combina los elementos de una forma específica de monoide.


Gabriel Gonzalez escribió en su blog gran información sobre por qué debería importarte, y realmente deberías preocuparte. Puedes leerlo here (y también ver this ).

Se trata de escalabilidad, arquitectura y diseño de API. La idea es que existe la "arquitectura convencional" que dice:

Combine varios componentes de tipo A para generar una "red" o "topología" de tipo B

El problema con este tipo de diseño es que a medida que su programa se escala, también lo hace su infierno cuando refactoriza.

Así que quieres cambiar el módulo A para mejorar tu diseño o dominio, así lo haces. Oh, pero ahora el módulo B & C que depende de A se rompió. Arregla B, genial. Ahora arreglas C. Ahora B se rompió de nuevo, ya que B también usó algunas de las funciones de C. Y puedo seguir con esto para siempre, y si alguna vez usaste OOP, tú también puedes.

Luego está lo que Gabriel llama la "arquitectura Haskell":

Combine varios componentes juntos de tipo A para generar un nuevo componente del mismo tipo A, de carácter indistinguible de sus partes sustituyentes

Esto resuelve el problema, también con elegancia. Básicamente: no coloque capas en sus módulos ni extienda para hacer módulos especializados.
En su lugar, combinar.

Así que ahora, lo que se recomienda es que en lugar de decir cosas como "Tengo varias X, así que hagamos un tipo que represente su unión", dices "Tengo múltiples X, así que combinémoslas en una X". O en inglés simple: "Hagamos tipos compositivos en primer lugar". (¿Sientes que los monoides están al acecho todavía?).

Imagine que desea crear un formulario para su página web o aplicación y tiene el módulo "Formulario de información personal" que creó porque necesitaba información personal. Más tarde descubrió que también necesita "Cambiar el formato de la imagen", así que escribió eso rápidamente. Y ahora dice que quiero combinarlos, así que hagamos un módulo de "Información personal y formulario de imagen". Y en las aplicaciones escalables de la vida real, esto puede y no está fuera de control. Probablemente no con los formularios, pero para demostrarlo, debe redactar y componer, por lo que terminará con "Información personal y Cambio de imagen, Cambiar contraseña, Cambiar estado, Administrar amigos, Administrar lista de deseos, Cambiar configuración de la vista, y No me extienda más & por favor & por favor detente! & STOP !!!! " módulo. Esto no es bonito, y tendrás que gestionar esta complejidad en la API. Ah, y si quieres cambiar algo, probablemente tenga dependencias. Entonces ... sí ... Bienvenido al infierno.

Ahora veamos la otra opción, pero primero veamos el beneficio porque nos guiará a ello:

Estas abstracciones se escalan ilimitadamente porque siempre conservan la combinabilidad, por lo tanto, nunca necesitamos colocar más abstracciones en la parte superior. Esta es una de las razones por las que debes aprender Haskell: aprendes a construir arquitecturas planas .

Suena bien, así que, en lugar de hacer el módulo "Formulario de información personal" / "Cambiar formulario de imagen", deténgase y piense si podemos hacer que algo aquí sea compostable. Bueno, podemos simplemente hacer un "Formulario", ¿verdad? También sería más abstracto.
Entonces puede tener sentido construir uno para todo lo que desee, combinarlos y obtener una forma como cualquier otra.

Y así, ya no obtienes un árbol complejo y desordenado, debido a la clave de que tomas dos formas y obtienes una forma. Entonces Form -> Form -> Form . Y como ya puede ver claramente, esta firma es una instancia de mappend .

La alternativa, y la arquitectura convencional probablemente se vería como a -> b -> c y luego c -> d -> e y luego ...

Ahora, con las formas no es tan desafiante; El reto es trabajar con esto en aplicaciones del mundo real. Y para hacerlo, simplemente pregúntese todo lo que pueda (porque vale la pena, como puede ver): ¿Cómo puedo hacer que este concepto sea compostable? y dado que los monoides son una forma tan simple de lograrlo (queremos que sea simple), primero pregúntese: ¿Cómo es este concepto un monoide?

Nota al margen: Afortunadamente, Haskell lo desanimará mucho para que extienda los tipos, ya que es un lenguaje funcional (sin herencia). Pero aún es posible hacer un tipo para algo, otro tipo para algo, y en el tercer tipo tener ambos tipos como campos. Si esto es por composición, ve si puedes evitarlo.


Un ejemplo muy simple es foldMap . Solo conectando diferentes monoides en esta función única, puede calcular:

  • el first y el last elemento,
  • la sum o el product de los elementos (de aquí también su promedio, etc.),
  • comprobar si all elementos o any tiene una propiedad determinada,
  • calcular el elemento máximo o mínimo,
  • asigne los elementos a una colección (como listas, sets , cadenas, Text , ByteString o ByteString Text ) y concatene juntos: todos son monoides.

Además, los monoides se pueden componer: si a y b son monoides, también lo es (a, b) . Por lo tanto, puede calcular fácilmente varios valores monoidales diferentes en una sola pasada (como la suma y el producto al calcular el promedio de los elementos, etc.).

Y aunque puede hacer todo esto sin monoides, utilizando foldr o foldl , es mucho más engorroso y, a menudo, menos efectivo: por ejemplo, si tiene un árbol binario equilibrado y desea encontrar su elemento mínimo y máximo, no puede realice ambos de manera efectiva con foldr (o ambos con foldl ), uno siempre será O (n) para uno de los casos, mientras que cuando se usa foldMap con los monoides apropiados, será O (log n) en ambos casos.

Y todo esto fue solo una sola función foldMap ! Hay muchas otras aplicaciones interesantes. Para dar uno, la exponenciación por escuadrado es una forma eficiente de calcular las potencias. Pero en realidad no está ligado a los poderes informáticos. Puede implementarlo para cualquier monoide, y si su <> es O (1) , tiene una forma eficiente de calcular n -times x <> ... <> x . Y, de repente, puede realizar una eficiente exponencia de matrices y calcular el n -ésimo número de Fibonacci con solo multipicaciones O (log n) . Ver times1p en semigrupo .

Ver también Monoides y Dedos .