This project uses Scala 3 macros to automate the process of comparing fields in case classes. The macro inspects the fields of two instances of the same case class and counts how many fields are non-null and have the same value. This approach reduces boilerplate code AND comes with the benefit of macro code generation, where the macro expands at compile time to generate the necessary comparison logic.
Last active
September 10, 2024 10:26
-
-
Save Sedose/2bc7f55e190678a25748449242f03ca4 to your computer and use it in GitHub Desktop.
Counting Matching Non-Null Fields with Macro. Reduce code duplication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scala.quoted.{quotes, Expr, Quotes, Type} | |
inline def compareEntity[T](a: T, b: T): Int = | |
${compareEntitiesImpl('a, 'b)} | |
def compareEntitiesImpl[T: Type](a: Expr[T], b: Expr[T])(using Quotes): Expr[Int] = { | |
import quotes.reflect.* | |
val tpe = TypeRepr.of[T] | |
val fields = tpe.typeSymbol.caseFields | |
val comparisons = fields.map { field => | |
val fieldName = field.name | |
val aField = Select(a.asTerm, field) | |
val bField = Select(b.asTerm, field) | |
'{ | |
val aVal = ${aField.asExpr} | |
val bVal = ${bField.asExpr} | |
if (aVal != null && bVal != null && aVal == bVal) 1 else 0 | |
} | |
} | |
comparisons.reduceLeft((acc, next) => '{ $acc + $next }) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@main def main(): Unit = { | |
val testCases = List( | |
(("USA", null, "New York"), ("USA", "NY", null)), // should count 1 | |
((null, null, null), (null, null, null)), // should count 0 | |
(("USA", null, null), ("USA", null, null)), // should count 1 | |
(("Germany", "Berlin", "Berlin"), ("Germany", "Berlin", null)), // should count 2 | |
(("France", "Paris", "Lyon"), ("France", "Paris", "Lyon")), // should count 3 | |
(("Canada", null, "Toronto"), ("Canada", "Ontario", "Toronto")), // should count 2 | |
((null, "California", null), (null, "California", "Los Angeles")), // should count 1 | |
(("Italy", "Rome", null), ("Italy", null, "Rome")), // should count 1 | |
(("Japan", "Tokyo", "Shibuya"), ("Japan", "Kyoto", "Osaka")), // should count 1 | |
(("Poland", null, "New York"), ("USA", "NY", null)), // should count 0 | |
) | |
testCases.foreach { case (a, b) => | |
val addrA = Address(a._1, a._2, a._3) | |
val addrB = Address(b._1, b._2, b._3) | |
println(compareEntity(addrA, addrB)) | |
} | |
} | |
case class Address(country: String, state: String, city: String) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment