The Rock Paper Scissors game forms a magma. An example for object-oriented programmers.

This article is part of a larger series about monoids, semigroups, and other group-like algebraic structures. In this article, you'll see an example of a magma, which is a binary operation without additional constraints.

Rock Paper Scissors #

When my first child was born, my wife and I quickly settled on a first name, but we couldn't agree on the surname, since we don't share the same surname. She wanted our daughter to have her surname, and I wanted her to have mine. We couldn't think of any rational arguments for one or the other, so we decided to settle the matter with a game of Rock Paper Scissors. I lost, so my daughter has my wife's surname.

Despite that outcome, I still find that Rock Paper Scissors is a great way to pick between two equally valid alternatives. You could also flip a coin, or throw a die, but most people have their hands handy, so to speak.

In C#, you can model the three shapes of rock, paper, and scissors like this:

public abstract class Rps { public readonly static Rps Rock = new R (); public readonly static Rps Paper = new P (); public readonly static Rps Scissors = new S (); private Rps() { } private class R : Rps { } private class P : Rps { } private class S : Rps { } // More members... }

I've seen more than one example where people use an

enum

enum

Rock

Paper

Scissors

enum to model the three shapes, but I believe that this is wrong, becauses have an order to them, including a maximum and a minimum value (by default,is implemented with a 32-bit integer). That's not how Rock Paper Scissors work, so instead, I chose a different model where, andare Singletons

This design works for the example, although I'm not entirely happy with it. The problem is that Rock, Paper, and Scissors should be a finite set, but by making Rps abstract, another developer could, conceivably, create additional derived classes. A finite sum type would have been better, but this isn't easily done in C#. In a language with algebraic data types, you can make a prettier implementation, like this Haskell example. F# would be another good language option.

Binary operation #

When playing the game of Rock Paper Scissors, each round is a throw that compares two shapes. You can model a throw as a binary operation that returns the winning shape:

public static Rps Throw( Rps x, Rps y) { if (x == Rock && y == Rock) return Rock; if (x == Rock && y == Paper) return Paper; if (x == Rock && y == Scissors) return Rock; if (x == Paper && y == Paper) return Paper; if (x == Paper && y == Scissors) return Scissors; if (x == Paper && y == Rock) return Paper; if (x == Scissors && y == Scissors) return Scissors; if (x == Scissors && y == Rock) return Rock; return Scissors; }

To a C# programmer, perhaps the method name Throw is bewildering, because you might expect the method to throw an exception, but I chose to use the domain language of the game.

Because this method takes two Rps values as input and returns an Rps value as output, it's a binary operation. Thus, you already know it's a magma, but could it, also, be another, stricter binary operations, such as a semigroup or quasigroup?

Lack of associativity #

In order to be a semigroup, the binary operation must be associative. You can easily demonstrate that Throw isn't associative by use of a counter-example:

[ Fact ] public void ThrowIsNotAssociative() { // Counter-example Assert .NotEqual( Rps .Throw( Rps .Throw( Rps .Paper, Rps .Rock), Rps .Scissors), Rps .Throw( Rps .Paper, Rps .Throw( Rps .Rock, Rps .Scissors))); }

This xUnit.net unit test passes, thereby demonstrating that Throw is not associative. The result of paper versus rock is paper, which, pitted against scissors yields scissors. On the other hand, paper versus the result of rock versus scissors is paper, because rock versus scissors is rock, and rock versus paper is paper.

Since Throw isn't associative, it's not a semigroup (and, by extension, not a monoid). Could it be a quasigroup?

Lack of invertibility #

A binary operation must be invertible in order to be a quasigroup. This means that for any two elements a and b , there must exist two other elements x and y that turns a into b .

This property must hold for all values involved in the binary operation - in this case Rock , Paper , and Scissors . A single counter-example is enough to show that Throw is not invertible:

[ Fact ] public void ThrowIsNotInvertible() { // Counter-example var a = Rps .Rock; var b = Rps .Scissors; Assert .False( Rps .All.Any(x => Rps .Throw(a, x) == b)); Assert .False( Rps .All.Any(y => Rps .Throw(y, a) == b)); }

This (passing) unit test utilises an All property on Rps :

public static IReadOnlyCollection < Rps > All { get { return new [] { Rock, Paper, Scissors }; } }

For a counter-example, pick Rock as a and Scissors as b . There's no value in All that satisfies the invertibility property. Therefore, Throw is not a quasigroup, either.

Lack of identity #

Since Throw is neither associative nor invertible, it's not really any named algebraic construct, other than a magma. It's neither group, semigroup, quasigroup, monoid, loop, groupoid, etc. Does it have any properties at all, apart from being a binary operation?

It doesn't have identity either, which you can illustrate with another counter-example:

[ Fact ] public void ThrowHasNoIdentity() { // Find all identity candidates for Rock var rockIdentities = from e in Rps .All where Rps .Throw(e, Rps .Rock) == Rps .Rock && Rps .Throw( Rps .Rock, e) == Rps .Rock select e; // Narrow for Paper var paperIdentities = from e in rockIdentities where Rps .Throw(e, Rps .Paper) == Rps .Paper && Rps .Throw( Rps .Paper, e) == Rps .Paper select e; // None of those candidates are the identity for Scissors var scissorIdentities = from e in paperIdentities where Rps .Throw(e, Rps .Scissors) == Rps .Scissors && Rps .Throw( Rps .Scissors, e) == Rps .Scissors select e; Assert .Empty(scissorIdentities); }

First, you use Rps.All to find all the values that behave as an identity element for Rps.Rock . Recall that the identity is an element that doesn't change the input. In other words it's a value that when combined with Rps.Rock in Throw still returns Rps.Rock . There are two values that fulfil that property: Rps.Rock and Rps.Scissors . Those are the two values contained in rockIdentities .

In order to be an identity, the value must behave as a neutral element for all possible values, so next, filter rockIdentities to find those elements that also behave as identities for Rps.Paper . Between Rps.Rock and Rps.Scissors , only Rps.Rock behaves like an identity for Rps.Paper , so paperIdentities is a collection containing only the single value Rps.Rock .

Is Rps.Rock an identity for Rps.Scissors , then? It's not. scissorIdentities is empty. There's no element in Rps.All that acts an identity for all values in Rps.All . Therefore, by brute force, the test ThrowHasNoIdentity demonstrates (as it says on the tin) that throw has no identity.

While Throw is neither associative, invertible, nor has identity, it does have at least one property: it's commutative. This means that the order of the input values doesn't matter. In other words, for any two Rps values x and y , this assertion always passes:

Assert .Equal( Rps .Throw(x, y), Rps .Throw(y, x));

Since Rps.Throw(x, y) is equal to Rps.Throw(y, x) , Throw is commutative.

The Rock Paper Scissors Throw operation is a commutative magma, but while, for example, we call an associative magma a semigroup, there's no fancy word for a commutative magma.

Next: Colour-mixing magma.