14 comments

[ 3.5 ms ] story [ 42.2 ms ] thread
This is the first 'F# For Fun and Profit' article I've read, I was very impressed with how the author (Scott Wlaschin?) breaks the subject of monoids down so clearly, something tells me that the author will be helping me out a lot in the coming months.
Monoid != Monad

There seems to be some confusion about which one is confusing. The hardest part of understanding monoids is accepting that yes, that's all there is to it. The fuss is about the other one.

Yes, I know that a monoid and a monad are two different terms, there's no confusion.

However, despite monoids being relatively simple, my first introduction to monoids was this...

http://gettingsharper.de/2015/03/03/understanding-monoids-us...

... which I didn't find beginner friendly. YMMV.

But what is the use of understanding monoids, other than as a step towards understanding monads?

No criticism... I read all of these submissions. But the ones which only cover monoids [0] leave me wondering, Okay, now what?

So I guess the confusion is mine.

[0] Other than to say that monads are "very different things".

> "But what is the use of understanding monoids, other than as a step towards understanding monads?"

I'm sorry, but I don't follow. It's just terminology for communicating more efficiently with other programmers (and mathematicians, in this case). They're both a group of ideas with a label. Why would one group only serve to introduce the second group?

You're completely right — I stand corrected. The OP covers practical usages at the end of the piece, as well as in a follow-up piece for that sole purpose. Thanks.
(comment deleted)
Did you ever realize that there is a similarity between addition (+) and zero (0) and multiplication (x) and 1? Can you guess what it is?

If you add something to zero, you get that same thing.

If you multiply something by one, you get that same thing.

That's what a monoid is: the generalization of the idea that if you can find an operator on a certain set (+ and x) and a special member of that set (0 and 1) that obey a few simple laws, then that thing is a monoid.

So we have identified two monoids: (Integer, +, 0) and (Integer, x, 1). Of course, this works with Floats and a few other number-based types.

I'll give you another example of a monoid: (String, append, ""), because if you add the empty string to a string, you get that same string back.

Now monads are a different topic. They are a monoid but understanding them requires a little bit more background, so I'll stop there.

Other "every day" monoids:

    - (Array, concat, [])
    - (Floating point numbers, min, +Infinity)
    - (Floating point numbers, max, -Infinity)
    - (Boolean, AND, true)
    - (Boolean, OR, false)
    - (Subsets of set S, union, {})
    - (Subsets of set S, intersection, S)
    - (Functions of type S → S, composition, the identity function)
You'll often see folks writing functions from Strings to Strings but returning _null_ or the like in the "empty" case instead of an empty String. Having them instead return the "identity" element of the relevant monoid will always result in a simpler, more composable interface.
> Having them instead return the "identity" element of the relevant monoid will always result in a simpler, more composable interface.

But there may be more than one relevant monoid, and thereby more than one identity element.

In the case of a function which returns an integer, should I return the multiplicative identity, or the additive one?

For a given problem, there's often some natural monoidal structure underneath the hood even if the operations aren't explicitly part of the type signature.

For example, it's "natural" for the sum of an empty list to be 0 but the product of an empty list to be 1. Why? So that the "hidden homomorphism" is preserved (here ++ is list concatenation):

        sum(listA ++ listB) == sum(listA) + list(listB)
    product(listA ++ listB) == product(listA) * product(listB)
For similar reasons, given some predicate P is some predicate, the any? should return false for an empty list and all? should return true so that the following hold:

    all?(P, listA ++ listB) == all?(P, listA) && all?(P, listB)
    any?(P, listA ++ listB) == any?(P, listA) || any?(P, listB)
Behind it all we're not only transforming values of TypeA into values of TypeB , we're doing it in a way that respects some underlying monoidal structure.

If you want to think of it in a more programmer-centric way, any time you have an operation that could be as a fold[1] there is an underlying monoidal structure. Monoids permit folding, folding implies the existence of some monoid.

[1]: http://en.wikipedia.org/wiki/Fold_(higher-order_function)

There's a typo in the first line of your first example.

    list(listB)
should read

    sum(listB)