In the article “The Algebra of Predicates and Sorting Functions” we showed how predicates can be made into a monoid. In short, we considered Bool to be a monoid with its operation given by && and identity true, and then defined Predicate<A> to be the type of functions (A) -> Bool. This allowed us to combine predicates in an expressive way, e.g. isEven <> isLessThan(10) is the predicate that literally expresses “is even and is less than 10”.

One thing missing from that discussion is that Bool has another monoidal structure, || with false. We will discuss how to rectify that in detail by defining something called a semiring.

Download a playground of the code in this article to follow along at home!

A tale of two monoids

First recall that a monoid is a type conforming to a protocol that specifies an associative binary operation and an identity element:

protocol Monoid {
  // **AXIOM** Associativity
  // For all a, b, c in Self:
  //    a <> (b <> c) == (a <> b) <> c
  static func <> (lhs: Self, rhs: Self) -> Self

  // **AXIOM** Identity
  // For all a in Self:
  //    a <> e == e <> a == a
  static var e: Self { get }
}

The Bool type naturally has two monoidal structures: one defined by (&&, true) and the other by (||, false). Neither of those is the right one, or more important than the other, yet Swift’s protocols force us to choose one for Bool (as do most programming languages out there!). One way around this is to wrap Bool in another type so that we can conform it to the protocol without interfering with Bool:

struct AndBool: Monoid {
  let value: Bool
  init(_ value: Bool) { self.value = value }

  static let e = AndBool(true)

  static func <> (lhs: AndBool, rhs: AndBool) -> AndBool {
    return .init(lhs.value && rhs.value)
  }
}

struct OrBool: Monoid {
  let value: Bool
  init(_ value: Bool) { self.value = value }

  static let e = OrBool(false)

  static func <> (lhs: OrBool, rhs: OrBool) -> OrBool {
    return .init(lhs.value || rhs.value)
  }
}

We now have two different versions of booleans: one that is a monoid under conjunction && and the other under disjunction ||. However, many times you don’t want to separate the ideas of && and || from Bool, but instead have one underlying structure that unifies them. This structure is called a semiring.

Semirings

A semiring is much like a monoid, but it has an additional operation on it, and the operations must play “nicely” together. Formally, a semiring is a type with two operations, denoted by + and *, and two distinguished elements zero and one as identities, satisfying a bunch of axioms:

protocol Semiring {
  // **AXIOMS**
  //
  // Associativity:
  //    a + (b + c) == (a + b) + c
  //    a * (b * c) == (a * b) * c
  //
  // Identity:
  //   a + zero == zero + a == a
  //   a * one == one * a == a
  //
  // Commutativity of +:
  //   a + b == b + a
  //
  // Distributivity:
  //   a * (b + c) == a * b + a * c
  //   (a + b) * c == a * c + b * c
  //
  // Annihilation by zero:
  //   a * zero == zero * a == zero
  //
  static func + (lhs: Self, rhs: Self) -> Self
  static func * (lhs: Self, rhs: Self) -> Self
  static var zero: Self { get }
  static var one: Self { get }
}

A (perhaps) shorter way to say the same thing is that 1.) S is a commutative monoid with (+, zero), 2.) a monoid with (*, one), 3.) * distributes over +, and 4.) zero annihilates with *.

We can now turn Bool into a semiring:

extension Bool: Semiring {
  static func + (lhs: Bool, rhs: Bool) -> Bool {
    return lhs || rhs
  }

  static func * (lhs: Bool, rhs: Bool) -> Bool {
    return lhs && rhs
  }

  static let zero = false
  static let one = true
}

It may seem weird to define + and * on Bool, but || and && really do behave like addition in multiplication since this distributivity property holds:

a && (b || c) == a && b || a && c
a *  (b +  c) == a *  b +  a *  c

Constructing new semirings from existing

Many of the constructions we did for monoids also work for semirings. For example, we can take the type of functions from a fixed type into a semiring:

struct FunctionS<A, S: Semiring> {
  let call: (A) -> S
}

This type also naturally forms a semiring by performing the operations point-wise:

extension FunctionS: Semiring {
  static func + (lhs: FunctionS, rhs: FunctionS) -> FunctionS {
    return FunctionS { lhs.call($0) + rhs.call($0) }
  }

  static func * (lhs: FunctionS, rhs: FunctionS) -> FunctionS {
    return FunctionS { lhs.call($0) * rhs.call($0) }
  }

  static var zero: FunctionS {
    return FunctionS { _ in S.zero }
  }

  static var one: FunctionS {
    return FunctionS { _ in S.one }
  }
}

A better definition of predicate

Rather than defining Predicate as the monoid of functions (A) -> Bool, we are going to defined as the semiring of functions (A) -> Bool:

typealias Predicate<A> = FunctionS<A, Bool>

And we can make Swift’s standard library understand these predicates by extending Sequence:

extension Sequence {
  func filtered(by p: Predicate<Element>) -> [Element] {
    return self.filter(p.call)
  }
}

Now we can combine predicates using both conjunctive and disjunctive operations and apply it to sequences:

let isEven = Predicate<Int> { $0 % 2 == 0 }
let isLessThan = { max in Predicate<Int> { $0 < max } }
let isMagic = Predicate<Int> { $0 == 13 }

Array(0...100).filtered(by: isEven * isLessThan(10) + isMagic)

This last expression describes getting only the integers from 0 to 100 that are even and less than 10, or are the magic number. It reads quite nicely, but it would be even better if we could use || and &&, so let’s define them!

func || <A> (lhs: Predicate<A>, rhs: Predicate<A>) -> Predicate<A> {
  return lhs + rhs
}

func && <A> (lhs: Predicate<A>, rhs: Predicate<A>) -> Predicate<A> {
  return lhs * rhs
}

Now we can write:

Array(0...100).filtered(by: isEven && isLessThan(10) || isMagic)

And now that reads great! We can even take it a step further by defining a prefix function ! for negating a predicate:

prefix func ! <A> (p: Predicate<A>) -> Predicate<A> {
  return .init { !p.call($0) }
}

Array(0...100).filtered(by: isEven && !isLessThan(10) || isMagic)

Conclusion

We have now seen how sometimes when a type seems to have two different monoidal structures on it, secretly it may be a semiring where the two structures play nicely together! There can still be times where a type has two different monoidal structures on it such that they do not form a semiring, in which case you must resort to wrapping the type in a new type that provides the custom conformance.

Exercises

1.) Previously we defined a concat function

func concat <M: Monoid> (_ xs: [M]) -> M {
  return xs.reduce(M.e, <>)
}

that concatenates an array of monoidal values into a single one by <>’ing them together. Semirings have two versions of this, so define em!

func add<S: Semiring>(_ xs: [S]) -> S {
  ???
}

func multiply<S: Semiring>(_ xs: [S]) -> S {
  ???
}

2.) A semiring (S, +, *, zero, one) naturally induces two different monoids, (S, +, zero) and (S, *, one), where you “forget“ one of the operations. The former is even a commutative monoid. Make two new types AdditiveMonoid<S: Semiring> and MultiplicativeMonoid<S: Semiring> that converts a semiring to its monoid form.

3.) Recall that a “commutative monoid” is a monoid A in which a <> b = b <> a for all a, b: A. Show that

struct EndoS<A: CommutativeMonoid> {
  let call: (A) -> A
}

is a semiring where + is given by pointwise <>-application of functions and * is given by function composition.

4.) A ring is a semiring that has the additional axiom that addition is invertible, i.e. there is a function inverse: (S) -> S such that a + inverse(a) = inverse(a) + a = S.zero for all a: S. Can you think of anything that form a ring? Does Bool form a ring?