April 24, 2016 / by Kim Hogeling / Web developer / @kimhogeling
Monoids in F#, JS and PHP
After watching Scott Wlaschin’s talk about functional programming patterns on vimeo I got interested in monoids. But the examples are in F# at which I’m not familiar. Let’s see how they can be used in JavaScript and PHP.
Scott Wlaschin is an IT architect, developer, F# trainer and consultant. He created the website fsharpforfunandprofit.com and is writing a book called understanding functional programming. Mr Wlaschin knows how to explain code design in an easy to follow and enjoyable way. Most of his talks that I have seen are about functional programming, secure code and good code design.
So, what is a monoid anyway?
You can skip this section, if you watched Scott Wlaschin’s Talk.
The following three rules apply to monoids:
- Closure: The result of combining two things is always another one of the things. This means that the input and output need to be of the same type.
- Associativity: When combining more than two things, which pairwise combination you do first doesn’t matter.
- Identity element: There is a special thing called “zero” such that when you combine any thing with “zero” you get the original thing back. Some examples:
0
for addition,1
for multiplying,""
for concatenation
The code in this section is written in JS. The practical example further below is written in F#, JS and PHP.
This is a monoid for numbers because the 3 rules apply:
- Closure:
add(1, 2)
equals3
- Associativity:
add(add(1, 2), add(3, 4))
equalsadd(1, add(add(2, 3), 4))
- Identity element:
0
, becauseadd(0, 1)
equals1
andadd(1, 0)
also equals1
Substract is not a monoid for numbers because 2 out of 3 rules do not apply:
- Closure:
substract(1, 2)
equals-1
- Associativity:
substract(substract(1, 2), substract(3, 4))
does not equalsubstract(1, substract(substract(2, 3), 4))
(0
does not equal6
). - Identity element: e.g.
0
is not an identity element, becausesubstract(0, 1)
equals-1
, butsubstract(1, 0)
does not equal-1
.
To use the concatenate
lambda as a monoid for strings, the list of numbers is first mapped to a list of strings.
Because lambdas are so easy to pass around without any side effects, we can make a list out of them. This list can be mapped to the results of each reduce result for each monoid.
Because concatenate
needs different input than numbers, it is not included in this list. Notice, that the values are not passed from lambda to lambda. This is not a chain, but just a simple list. To create a chain the amount of inputs need to match the amount of outputs. That is not the case for our current lambdas, because they accept two arguments and return one value.
Practical example: Sum of products in a wishlist
Let’s use a more realistic example. Instead of taking Scott Wlaschin’s example, I thought of one, which could be useful for the shopping24 wishlist. This wishlist contains the products that are “starred” by the visitor. I want to sum the prices and shipping costs of the products. This is achieved by combining the wishlist products with help of F# List.reduce
, JS Array.prototype.reduce
and PHP array_reduce
and a monoid called pairAdd
.
F# version
JS version
PHP version
I find this solution slick and easy to read. Having pairAdd
to be a monoid for any object with the keys price
and shipping
is simple and doesn’t cause any side effects.
In contrast, the common solution would be to have a Wishlist class which would have a list of products and a method to sum the prices by iterating over the products. But that would create a lot of overhead and would also require noisy variables like i
and length
.
Anyway, I’m sure I’ll watch more of Scott Wlaschin’s talks. Currently (2016-04-24) Vimeo has 9 uploads of each about an hour. If you care about good code design and security, I recommend Designing with capabilities for fun and profit. So grab some Mate and Nachos and enjoy!