Kone: Relations
This module provides contexts of basic relations between objects, their basic implementations, and basic utilities.
This module introduces several "contexts". The term "context" is described in "Contexts" module.
Applying the dependencies
- Gradle Kotlin DSL
- Gradle Groovy DSL
- Maven
dependencies {
implementation("dev.lounres:kone.relations:0.0.0-experiment")
}
dependencies {
implementation 'dev.lounres:kone.relations:0.0.0-experiment'
}
<dependency>
<groupId>dev.lounres</groupId>
<artifactId>kone.relations</artifactId>
<version>0.0.0-experiment</version>
</dependency>
Relation contexts: Equality
, Order
, Hashing
There are three basic contexts that are used a lot all over the Kone library:
Equality
that checks equality between instances of some specified type.Order
that compares instances of some specified type.Hashing
that represents a hashing function on instances of specified type.
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:
- "Default equality". It delegates equality to good old
equals
method. - "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.