Skip to content
loading...
Discussion
markdown guide
 

Monad or monoid?

The technical definition is a monoid is a semigroup with an identity. Which is not very ELI5.

So: a monoid is "a bunch of stuff, and a thing we do with that stuff, that behaves pretty intuitively." Which is vague as hell. But it's meant to be. Very frequently, things that behave like this "feel" like addition.

Like, say, shoving a bunch of piles of sticks together.

  1. If I have some sticks, and I shove them together, what's in the pile? Sticks, right? Yup.

  2. What if I shove them into smaller piles first and then sort of regroup those piles around themselves and then shove them together? Is it still a pile of sticks? What if I shove the sticks on the left first before I do the rest? Yup. Is it basically the same pile of sticks? Yup.

  3. If I have a bunch of sticks and shove No Sticks at it... did anything change? Nope.

The first two are what makes it a semigroup.

#1 is closure under a binary operation. Sure, there's lots of sticks, but I could split it into two groups, or add one stick at a time, or whatever. When I operate on the sticks using Shove to bring them together, I still have things that are sticks at the end. They didn't turn into frogs, I didn't carve them.

Fire would be not only non-binary, but also not closed -- if I use fire on the sticks, do I have sticks at the end? Well, no, not really. Burnt sticks, sure -- which noticeably, is sticks that have had the fire operation on them.

#2 is associativity. That means that the order we evaluate things in doesn't matter.

#3 is the identity. This is the thing that is analogous to zero -- if you take it, and apply your operation on it and something, nothing changes. So No Sticks would be it here.

From a programming perspective, strings concatenation is a good not-math example.

What's the binary operation? Concatenation. Joining things together. (Remember what I said about feeling like addition!) You operate only on two pieces at a time. If you had a million words, you'd still do it by adding one to another etc. a + b => ab

Is it closed under that operation? Closure means "I have stuff of this kind, do something to it, and still have that type at the end." So, if we take strings at stick them together, we have... a long string! So, yup, it's a string. Cool! So it's closed under our operation.

Is it associative? Yup! And remember, this doesn't mean that the order doesn't matter--but that the order of evaluation doesn't matter. he + llo gives the same result as h + ello or h + el + lo or h + (el + lo) or (h + el) + lo or ((h + (e + l) + l) + o are all hello at the end. (Note that 'order does matter' here would be -- is he + llo the same as lh + elo? Nope, hello is not lhelo.

Is there an identity for this operation? Yup -- the empty string. apple + "" is apple. Still a string. "" is a string, too, just one without anything in it. So we can use it for our operation, still get a string -- the same string we started with, in fact!

 

Now that I know the difference. I know that my picture below is wrong.

Free Monoid!

 

I'll do you one better - why is a monoid?

Others have explained the mathematical concept, and math is cool and all - but adding numbers and concatenating strings are not unique to functional programming - you need to do this stuff in procedural programming too. So why does the functional programming literature need to specifically discuss the nature of monoids?

Because of actions.

In procedural programming, actions are easy - all your statements are actions. You run a statement to perform an action, whether that action is to set a variable, print something to a file, send an HTTP request or whatever. Expressions are just there to help you set the parameters of your actions.

Functional programming, on the other hand, are about expressions. A functional program, rather then being a long sequence of actions, is composed of a big, compound expression to be resolved. There are no statements - doing actions for the sake of their side-effects is considered impure.

So, in functional programming you will replace actions like variable settings and looping with clever structuring of function calls. But what about the cases where you have to do an action for it's side-effects - like writing to a file? Even in pure functional languages like Haskell you need to be able to do those!

The solution is to have values that represent actions. These values can be created in the functional code, and then be passed to a special component that can run impure code. That way you can do side-effects while maintaining functional purity.

In order to do that - actions are being treated at monoids under the operation of composition! To understand how that operation works we'll have to discuss monads, but for now let's just say that A + B means "Do A and then do B".

So how are actions monoids? Let us examine the definition:

  1. Are they closed under the binary operation? Yes - if I compose two actions, I get a new action ("do them both!").

  2. Are they associative? Yes - (A + B) + C is the same as A + (B + C), because they both do A, B, and C in that order.

  3. And what about the identity? The identity is NOP - do nothing. A + NOP means "do A then do nothing" which is the same as just "do A". Same for NOP + A.

So now that actions are monoids, your functional code can mix and match them to create the sequence of side-effects you want to run. This is still purely functional because you are not running anything yet - you are merely composing a big action to send to the executor to run.

 

I don’t really like the term monoid because it sounds like something out of quantum field theory when the reality is pretty simple.

The two main characteristics of monoids are associativity and identity. For example, let's consider numbers under addition:

(3 + 2) + 6 = 3 + (2 + 6) (associativity)
100 + 0 = 100 (identity)
0 + 100 = 100 (identity)

Numbers are monoids under addition where 0 is the identity element (adding 0 to a number produces the original number).

Try it with multiplication over numbers and concatenation over lists, since those are also monoids. What’s the identity element in each case?

How about subtraction?

Note that you can't say that numbers (or anything else) are or are not monoids without specifying a binary operation (i.e. an operation with 2 arguments) on them as well. Something may be a monoid under a given operation but not a monoid under a different operation.

Classic DEV Post from Jun 25 '18

Module Monday: Parallax scroll, Image gallery, Sidenav, & more

Free, open-source modules for your next project

Nick Taylor (he/him) profile image
Senior software developer at DEV. Caught the live coding bug on Twitch.