typelevel/algebra

Add BoolRing type class

TomasMikula opened this issue · 7 comments

This is a proposal to add type class for Boolean rings.

A Boolean ring is a ring that satisfies x times x = x for all x (i.e. every element is idempotent with respect to multiplication).

There isn't general consensus whether a Boolean ring assumes 1 (multiplicative identity). Some sources refer to Boolean ring as a Boolean algebra without identity. However, since algebra.ring.Ring has 1, it makes sense for BoolRing to include 1 as well. Adding a Boolean ring without 1 could be addressed as a separate issue, possibly calling it BoolRng.

The above property implies:

  1. Every Boolean ring is a commutative ring.
  2. Every element is its own additive inverse, i.e. x plus x = 0.

As a consequence of 1., BoolRing should extend algebra.ring.CommutativeRing.

As a consequence of 2., BoolRing can implement negate to be

def negate(x: A): A = x

A Boolean ring is equivalent to a Boolean algebra, with conversions both ways.

Bool#asCommutativeRing already returns a Boolean ring. However, this fact is currently not captured by the return type.

Should we also have a method .asBooleanAlgebra or should this also subclass Bool?

non commented

I slightly prefer .asBooleanAlgebra, since it avoids possible ambiguities between a Bool[A] and its corresponding BoolRing[A] (and also so that Bool[A].asCommutativeRing.asBooleanAlgebra eq Bool[A].

But if you all prefer subclassing I think that would be OK here as well.

For a subclassing solution, it would be sufficient for Bool to extend CommutativeRing—no BoolRing needed. That was my original suggestion. Then @non pointed out that it could conflict with other, more natural implicit rings for the same type.

I will also note that if/when we add the versions without identity (i.e. a Boolean ring without identity, equivalent to a Boolean algebra without identity and relative complement instead of (absolute) complement), whether they are going to be the same type class or two different type classes will have to copy this decision.

I'm fine with either solution.

I tend to agree with @non that subclassing can be difficult when combined with implicit resolution. We do it in some cases, not others, and I guess there is no avoiding the judgement call.

If we did the subclassing route, you could make some wrapper class, case class BoolInt(toInt: Int) and define Bool[BoolInt] to remove the ambiguity, but this adds boxing costs that I don't see how to remove (insert discussion about zero cost type aliases that give compile errors when misused).

That said, I think the .as... approach with the law that it unwraps y.asX.asY eq y is a good approach.

Yeah, I don't think either design is better in all respects than the other, so I'm OK with taking that route. The type class instances are then free to implement both Bool and BoolRing by the same instance and have both asRing and asBool return this.

Hi, if no-one is working on this yet, I can try to put together a pull request.

non commented

Go for it! 😄