I have a case class that represent a Person.
case class Person(firstName: String, lastName: String)
I need to perform person comparison based on the first name and last name in case insensitive way, such as:
Person("John", "Doe") == Person("john", "Doe") // should return true
or in a Seq
Seq(Person("John", "Doe")).contains(Person("john", "Doe")
The simplest way is to override equals and hashCode methods inside Person case class, but as overwriting equals and hashCode in case class is frowned upon, what will be the best way to do that in a clean way.
Can somebody recommend an idiomatic way to solve this case sensitivity issue?
Thanks, Suriyanto
I wouldn't compromise the original meaning of equals
, hashCode
and, consequently, ==
for a case class. IMHO the most idiomatic solution here from a functional programming point of view is to use a type class:
case class Person(firstName: String, lastName: String)
trait Equal[A] {
def eq(a1: A, a2: A): Boolean
}
object Equal {
def areEqual[A : Equal](a1: A, a2: A): Boolean = implicitly[Equal[A]].eq(a1, a2)
implicit object PersonEqual extends Equal[Person] {
override def eq(a1: Person, a2: Person): Boolean = a1.firstName.equalsIgnoreCase(a2.firstName) &&
a1.lastName.equalsIgnoreCase(a2.lastName)
}
}
In a REPL session:
scala> import Equal.areEqual
import Equal.areEqual
scala> val p1 = Person("John", "Doe")
p1: Person = Person(John,Doe)
scala> val p2 = p1.copy(firstName = "john")
p2: Person = Person(john,Doe)
scala> areEqual(p1, p2)
res0: Boolean = true
scala> val p3 = p1.copy(lastName = "Brown")
p3: Person = Person(John,Brown)
scala> areEqual(p1, p3)
res1: Boolean = false
This way if you need to provide a different equality meaning for Person
in a given context you can just implement your version of Equal[Person]
without touching anything else. E.g.: In a given point of your code two Person
instances are equal if they have the same last name:
implicit object PersonLastnameEqual extends Equal[Person] {
override def eq(a1: Person, a2: Person): Boolean = a1.lastName.equalsIgnoreCase(a2.lastName)
}
REPL session:
scala> val p1 = Person("John", "Doe")
p1: Person = Person(John,Doe)
scala> val p2 = p1.copy(firstName = "Mary")
p2: Person = Person(Mary,Doe)
scala> areEqual(p1, p2)
res0: Boolean = true
You could perform some "normalization" at Person construction:
sealed trait Person {
def firstName:String
def lastName:String
}
object Person {
def apply(firstName:String, lastName:String):Person = PersonNormalized(firstName.toLowerCase, lastName.toLowerCase)
private case class PersonNormalized(firstName:String, lastName:String) extends Person
}
Its up to you to decide if it better than overriding equals and hashCode
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With