# Rock Paper Scissors magma by Mark Seemann

*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 to model the three shapes, but I believe that this is wrong, because `enum`

s have an order to them, including a maximum and a minimum value (by default, `enum`

is implemented with a 32-bit integer). That's not how Rock Paper Scissors work, so instead, I chose a different model where `Rock`

, `Paper`

, and `Scissors`

are 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.

### Commutativity #

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.

### Summary #

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.