-
Notifications
You must be signed in to change notification settings - Fork 0
Equals, HashCode and Ord
FireflyLang types are not comparable unless you specify they are, for example, the following code:
class Person {
val name: String
val age: Int
}
fun main(args) {
val person = Person(name = "Albert", age = 18)
val person2 = Person(name = "Albert", age = 18)
assert(person == person2)
}
Will fail to compile with the following error: Could not compare 'person == person2' because type Person does not implement Eq type., the same applies for Hash dependants collections, such as hashMap and hashSet:
class Person {
val name: String
val age: Int
}
fun main(args) {
val person = Person(name = "Albert", age = 18)
val person2 = Person(name = "Albert", age = 18)
val byName = hashMap<String, Person>() // Fails to compile because type Person does not implement Eq+Hash type.
}
To solve this, you need to explicitly implement Eq and Hash types:
class Person : Eq, Hash {
val name: String
val age: Int
override fun eq(other: Person) = this.name == other.name && this.age == other.age
override fun hash() = hashCode(this.name, this.age)
}
If you look into Eq and Hash types, you will be able to see the following code:
type Eq {
fun eq(other: this): Boolean
}
type Hash = Int {
fun hash(): Hash
}
Also, for sort comparison, FireflyLang requires the Ord type to be implemented. Implementing the Ord type is equal to implementing Comparable type (and they are the same in the end).
class Person : Eq, Hash, Ord {
val name: String
val age: Int
override fun eq(other: Person) = this.name == other.name && this.age == other.age
override fun hash() = hashCode(this.name, this.age)
override fun compare(other: Person) = (this.name <> other.name) || (this.age <> other.age) // First compare by name, if names are equals, then compare by age.
}
Note that, when implementing Ord, you could compare more than one property, and you must do it, because Ord implementation must respect Eq and Hash behavior, in other words, if a.eq(b) then a <> b == 0 and a.hash() == b.hash().
If you want to do a partial comparison (eg a comparison only for Sorting Algorithms), you could use individual Ord implementation, for example:
class Person : Eq, Hash, Ord {
val name: String
val age: Int
override fun eq(other: Person) = this.name == other.name && this.age == other.age
override fun hash() = hashCode(this.name, this.age)
override fun compare(other: Person) = (this.name <> other.name) || (this.age <> other.age) // First compare by name, if names are equals, then compare by age.
override fun compare(other: Person) for name = this.name <> other.name
override fun compare(other: Person) for age = this.age <> other.age
}
And when sorting, use: personCollection.sort(by name) or personCollection.sort(by age). Also you could use aliased implementation:
class Person : Eq, Hash, Ord {
val name: String
val age: Int
override fun eq(other: Person) = this.name == other.name && this.age == other.age
override fun hash() = hashCode(this.name, this.age)
override fun compare(other: Person) = (this.name <> other.name) || (this.age <> other.age) // First compare by name, if names are equals, then compare by age.
override fun compare(other: Person) as naturalOrder = this.name <> other.name
}
And when sorting, use: personCollection.sort(with naturalOrder).
If you look into Ord type, you could see the following code:
type Ord = Int {
fun compare(other: this): Ord
}
And in sort function:
type Sortable<E: Ord> {
fun sort(ord: Ord = E) // `ord: Ord = E` defaults to the main implementation of `Ord` type, while using `by` or `with` selects a different `Ord` implementation.
}
Note that, Sortable is a type, and not an interface. Types have more features and describes an indirect implementation (does not require : operator, while interface and abstract class does require a direct implementation, so the Person class could be rewritten as:
class Person {
val name: String
val age: Int
fun eq(other: Person) = this.name == other.name && this.age == other.age
fun hash() = hashCode(this.name, this.age)
fun compare(other: Person) = (this.name <> other.name) || (this.age <> other.age) // First compare by name, if names are equals, then compare by age.
fun compare(other: Person) as naturalOrder = this.name <> other.name
}
And does still meets the criteria of implementing Eq, Hash and Ord.
WIP