Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is practical use of monoids?

Tags:

I'm reading Learn You a Haskell and I've already covered applicative and now I'm on monoids. I have no problem understanding the both, although I found applicative useful in practice and monoid isn't quite so. So I think I don't understand something about Haskell.

First, speaking of Applicative, it creates something like uniform syntax to perform various actions on 'containers'. So we can use normal functions to perform actions on Maybe, lists, IO (should I have said monads? I don't know monads yet), functions:

λ> :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 

So applicative is an abstraction. I think we can live without it but it helps express some ideas mode clearly and that's fine.

Now, let's take a look at Monoid. It is also abstraction and pretty simple one. But does it help us? For every example from the book it seems to be obvious that there is more clear way to do things:

λ> :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 

So we have noticed some patterns and created new type class... Fine, I like math. But from practical point of view, what the point of Monoid? How does it help us better express ideas?

like image 490
Mark Karpov Avatar asked Oct 07 '14 07:10

Mark Karpov


People also ask

What are monoids used for?

Monoids are important because it gives a sequence of the elements of the type, we can combine them with the function in any order. This is because of the associativity of the function. Therefore, we can use the higher-order functions foldLeft, foldRight, fold, and aggregate 1 with any monoid.

Why use monoids Haskell?

One answer is that thinking in terms of monoids can be very beneficial to parallelism and efficient data structures. Using the Monoid interface in Haskell allows you to leverage the many convenient functions that work with them.

What is a Monoid functional programming?

The term Monoid comes from category theory. It describes a set of elements which has 3 special properties when combined with a particular operation, often named concat : The operation must combine two values of the set into a third value of the same set.

What are monoids in Haskell?

As we can see, Monoid in Haskell is a typeclass that has three methods: mempty , mappend , and mconcat . mempty is a value that doesn't impact the result when used together with the binary operation. In other words, it's the identity element of the monoid. mappend (or <> ) is a function that puts two monoids together.


1 Answers

Gabriel Gonzalez wrote in his blog great information about why you should care, and you truly should care. You can read it here (and also see this).

It's about scalability, architecture & design of API. The idea is that there's the "Conventional architecture" that says:

Combine a several components together of type A to generate a "network" or "topology" of type B

The issue with this kind of design is that as your program scales, so does your hell when you refactor.

So you want to change module A to improve your design or domain, so you do. Oh, but now module B & C that depend on A broke. You fix B, great. Now you fix C. Now B broke again, as B also used some of C's functionality. And I can go on with this forever, and if you ever used OOP - so can you.

Then there's what Gabriel calls the "Haskell architecture":

Combine several components together of type A to generate a new component of the same type A, indistinguishable in character from its substituent parts

This solves the issue, elegantly too. Basically: do not layer your modules or extend to make specialized ones.
Instead, combine.

So now, what's encouraged is that instead of saying things like "I have multiple X, so let's make a type to represent their union", you say "I have multiple X, so let's combine them into an X". Or in simple English: "Let's make composable types in the very first place." (do you sense the monoids' lurking yet?).

Imagine you want to make a form for your webpage or application, and you have the module "Personal Information Form" that you created because you needed personal information. Later you found that you also need "Change Picture Form" so quickly wrote that. And now you say I want to combine them, so let's make a "Personal Information & Picture Form" module. And in real life scalable applications this can and does get out of hand. Probably not with forms but to demonstrate, you need to compose and compose so you will end up with "Personal Information & Change Picture & Change Password & Change Status & Manage Friends & Manage Wishlist & Change View Settings & Please don't extend me anymore & please & please stop! & STOP!!!!" module. This is not pretty, and you will have to manage this complexity in the API. Oh, and if you want change anything - it probably has dependencies. So.. yeah.. Welcome to hell.

Now let's look at the other option, but first let's look at the benefit because it will guide us to it:

These abstractions scale limitlessly because they always preserve combinability, therefore we never need to layer further abstractions on top. This is one reason why you should learn Haskell: you learn how to build flat architectures.

Sounds good, so, instead of making "Personal Information Form" / "Change Picture Form" module, stop and think if we can make anything here composable. Well, we can just make a "Form", right? would be more abstract too.
Then it can make sense to construct one for everything you want, combine them together and get one form just like any other.

And so, you don't get a messy complex tree anymore, because of the key that you take two forms and get one form. So Form -> Form -> Form. And as you can already see clearly, this signature is an instance of mappend.

The alternative, and the conventional architecture would probably look like a -> b -> c and then c -> d -> e and then...

Now, with forms it's not so challenging; the challenge is to work with this in real world applications. And to do that simply ask yourself as much as you can (because it pays off, as you can see): How can I make this concept composable? and since monoids are such a simple way to achieve that (we want simple) ask yourself first: How is this concept a monoid?

Sidenote: Thankfully Haskell will very much discourage you to extend types as it is a functional language (no inheritance). But it's still possible to make a type for something, another type for something, and in the third type to have both types as fields. If this is for composition - see if you can avoid it.

like image 90
MasterMastic Avatar answered Sep 28 '22 02:09

MasterMastic