Skip to main content

Kone: Relations

This module provides contexts of basic relations between objects, their basic implementations, and basic utilities.

About learning order

This module introduces several "contexts". The term "context" is described in "Contexts" module.

Applying the dependencies

build.gradle.kts
dependencies {
implementation("dev.lounres:kone.relations:0.0.0-experiment")
}

Relation contexts: Equality, Order, Hashing

There are three basic contexts that are used a lot all over the Kone library:

  1. Equality that checks equality between instances of some specified type.
  2. Order that compares instances of some specified type.
  3. Hashing that represents a hashing function on instances of specified type.
info

The contexts may be defined on subset of specified type that cannot be expressed as a type.

For example, a context of type Order<Int> may be defined on all integers except for zero. Non-zero integers cannot be expressed as a type. But still, one can just mention this in the context's documentation.

Equality

Regardless of all specialised implementations of Equality interface, there are two common implementations:

  1. "Default equality". It delegates equality to good old equals method.
  2. "Absolute equality". It delegates equality to reference equality operator.

You can acquire the implementation via defaultEquality<MyType>() and absoluteEquality<MyType>(). But you also can create your own equality in place with Equality { left, right -> ... } builder function.

Also, there are contextual functions for the context's usage: equalsTo and eq for equality, notEqualsTo and neq for inequality.

Hashing

Regardless of all specialised implementations of Hashing interface, there is one common implementation: "default hashing". It delegates hashing to good old hashCode method.

You can acquire the implementation via defaultHashing<MyType>(). But you also can create your own hashing in place with Hashing { element -> ... } builder function.

Also, there is a contextual function for the context's usage: hash.

Order

This context appears with its own ComparisonResult (instead of Int) and Comparator.

Regardless of all specialised implementations of Order interface, there is one common implementation on self-comparable types: "default order". It delegates comparison to Comparable.compareTo method.

You can acquire the implementation via defaultOrder<MyComparableType>(). But you also can create your own order in place with Order { left, right -> ... } builder function.

Also, there are contextual functions for the context's usage like greaterThan and leq.

Domain context: Reification

Sometimes there is a need to check that the argument lies in the domain of a context. (I.e. you can equate two elements via Equality and there won't be any error because of incompatibility of the elements and the Equality instance.)

Very simple use case is set (or map). To define the uniqueness of the elements in the set we have to use some Equality instance. But Equality is contravariant. So there is no legitimate to make set interface covariant.

But Kotlin stdlib's Set<Element> is covariant despite having contravariant method contains. How does this work? Well, the answer is simple. contains method's non-covariance is just suppressed by @UnsafeVariance annotation. So it can accidentally receive parameter not of the type Element. But regardless, this object has equals method (because Any is a supertype) that makes the contains method work even with wrong types.

But in our case Equality<Element>'s equalsTo is used, and it may throw exception due to failed cast to Element type. So what do we do? We somehow have to check that the element can be accepted by the Equality instance. That's why this module introduces Reification interface. The interface represents a checker that says if the element lies in other context's domain. So the set's contains method can now look like

override fun contains(element: Element): Boolean = element in elementReification && <checking if the element equals to some element of the set with respect to elementEquality>

A lot of real use-cases in Kone have a lot of duplicates of the same line

override fun contains(element: Element): Boolean = element in elementReification && super.contains(element)

That's why in collections module there are separate interfaces Set<Element> and ReifiedSet<out ELement> : Set<Element>. The first one does not depend on Reification instance, while the second one depends.