shopping24 tech blog

s is for shopping

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:

  1. 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.
  2. Associativity: When combining more than two things, which pairwise combination you do first doesn’t matter.
  3. 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.

// define lambda which is a monoid for numbers
const add = (augend, addend) => augend + addend; // [Function]

This is a monoid for numbers because the 3 rules apply:

  • Closure: add(1, 2) equals 3
  • Associativity: add(add(1, 2), add(3, 4)) equals add(1, add(add(2, 3), 4))
  • Identity element: 0, because add(0, 1) equals 1 and add(1, 0) also equals 1
// this lambda can NOT be used as a monoid
const substract = (minuend, substrahend) => minuend - substrahend; // [Function]

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 equal substract(1, substract(substract(2, 3), 4)) (0 does not equal 6).
  • Identity element: e.g. 0 is not an identity element, because substract(0, 1) equals -1, but substract(1, 0) does not equal -1.
// define three simple lambdas which are monoids for numbers and the third is for strings
const add = (augend, addend) => augend + addend; // [Function]
const multiply = (multiplier, multiplicand) => multiplier * multiplicand; // [Function]
const concatenate = (str1, str2) => str1 + str2; // [Function]

const numbers = [80, 2, 4, 4, 1, 2];

// apply each individual lambda on pairs of numbers from the array via reduce
numbers.reduce(add); // sum of 93
numbers.reduce(multiply); // product of 5120
numbers.map((nr) => "" + nr).reduce(concatenate); // "8024412"

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.

const lambdas = [add, multiply];
lambdas.map((f) => numbers.reduce(f)); // [ 93, 5120 ]

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

type Product = { Price:float; Shipping;float }

let wishlist = [
    { Price: 10; Shipping: 2 }
    { Price: 40; Shipping: 3 }
    { Price: 80; Shipping: 5 } ]

let pairAdd pair1 pair2 =
    let price = pair1.Price + pair2.Price;
    let shipping = pair1.Shipping + pair2.Shipping;
    {Price=price; Shipping=shipping}

wishlist |> List.reduce pairAdd
// {Price=130; Shipping=10}

JS version

const wishlist = [
    { price: 10, shipping: 2 },
    { price: 40, shipping: 3 },
    { price: 80, shipping: 5 }
]

const pairAdd = (pair1, pair2) => ({
    price: pair1.price + pair2.price,
    shipping: pair1.shipping + pair2.shipping
})

wishlist.reduce(pairAdd)
// { price: 130, shipping: 10 }

PHP version

<?php

$wishlist = [
    [ 'price' => 10, 'shipping' => 2 ],
    [ 'price' => 40, 'shipping' => 3 ],
    [ 'price' => 80, 'shipping' => 5 ]
];

function pairAdd ($pair1, $pair2) {
    return [
        'price' => $pair1["price"] + $pair2["price"],
        'shipping' => $pair1["shipping"] + $pair2["shipping"]
    ];
}

// in php array_reduce needs a start thing
array_reduce($wishlist, 'pairAdd', [ 'price' => 0, 'shipping' => 0 ]);
// [ "price" => 130, "shipping" => 10 ]

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!