Skip to content

Instantly share code, notes, and snippets.

@Odomontois
Forked from Sedose/AAA. MacroDef.scala
Created September 10, 2024 12:32
Show Gist options
  • Save Odomontois/9c2c057ad4815986afa895c0869c8dad to your computer and use it in GitHub Desktop.
Save Odomontois/9c2c057ad4815986afa895c0869c8dad to your computer and use it in GitHub Desktop.
ChatGPT generated. Working. Scala macro for entities custom comparison logic + Analogs without Macro

Scala Macro for Custom Entity Comparison

This gist contains a Scala 3 macro that compares two entities field by field, automatically skipping any fields where exactly one of the values is null. The comparison logic is generated at compile time using Scala's quoted API, which ensures no runtime reflection overhead.

Features

  • Compare two entities of any type.
  • Skip fields with null values based on predefined business rules.
  • Avoid runtime reflection by generating code at compile time.

Example

The example compares two Address objects:

@main def main(): Unit = {
  val testCases = List(
    (("USA", null, "New York"), ("USA", "NY", null)),                  // true
    ((null, null, null), (null, null, null)),                          // true
    (("USA", null, null), ("USA", null, null)),                        // true
    (("Germany", "Berlin", "Berlin"), ("Germany", "Berlin", null)),    // true
    (("France", "Paris", "Lyon"), ("France", "Paris", "Lyon")),        // true
    (("Canada", null, "Toronto"), ("Canada", "Ontario", "Toronto")),   // true
    ((null, "California", null), (null, "California", "Los Angeles")), // true
    (("Italy", "Rome", null), ("Italy", null, "Rome")),                // true
    (("Japan", "Tokyo", "Shibuya"), ("Japan", "Kyoto", "Osaka")),      // false
    (("Poland", null, "New York"), ("USA", "NY", null)),               // false
  )

  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)
import scala.quoted.*
inline def compareEntity[T](a: T, b: T): Boolean =
${compareEntitiesImpl('a, 'b)}
def compareEntitiesImpl[T: Type](a: Expr[T], b: Expr[T])(using Quotes): Expr[Boolean] = {
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}
aVal == bVal || aVal == null || bVal == null
}
}
comparisons.reduceLeft((acc, next) => '{ $acc && $next })
}
@main def main(): Unit = {
val testCases = List(
(("USA", null, "New York"), ("USA", "NY", null)), // true
((null, null, null), (null, null, null)), // true
(("USA", null, null), ("USA", null, null)), // true
(("Germany", "Berlin", "Berlin"), ("Germany", "Berlin", null)), // true
(("France", "Paris", "Lyon"), ("France", "Paris", "Lyon")), // true
(("Canada", null, "Toronto"), ("Canada", "Ontario", "Toronto")), // true
((null, "California", null), (null, "California", "Los Angeles")), // true
(("Italy", "Rome", null), ("Italy", null, "Rome")), // true
(("Japan", "Tokyo", "Shibuya"), ("Japan", "Kyoto", "Osaka")), // false
(("Poland", null, "New York"), ("USA", "NY", null)), // false
)
testCases.foreach { case (a, b) =>
val addrA = Address(a._1, a._2, a._3)
val addrB = Address(b._1, b._2, b._3)
println(compareProduct(addrA, addrB))
}
}
def compareProduct[A](a: A, b: A)(using m: Mirror.ProductOf[A]): Boolean = {
a.asInstanceOf[Product].productIterator.zip(b.asInstanceOf[Product].productIterator).forall {
case (aVal, bVal) => aVal == bVal || aVal == null || bVal == null
}
}
case class Address(country: String, state: String, city: String)
@main def main(): Unit = {
val testCases = List(
(("USA", null, "New York"), ("USA", "NY", null)), // true
((null, null, null), (null, null, null)), // true
(("USA", null, null), ("USA", null, null)), // true
(("Germany", "Berlin", "Berlin"), ("Germany", "Berlin", null)), // true
(("France", "Paris", "Lyon"), ("France", "Paris", "Lyon")), // true
(("Canada", null, "Toronto"), ("Canada", "Ontario", "Toronto")), // true
((null, "California", null), (null, "California", "Los Angeles")), // true
(("Italy", "Rome", null), ("Italy", null, "Rome")), // true
(("Japan", "Tokyo", "Shibuya"), ("Japan", "Kyoto", "Osaka")), // false
(("Poland", null, "New York"), ("USA", "NY", null)), // false
)
testCases.foreach { case (a, b) =>
val addrA = Address(a._1, a._2, a._3)
val addrB = Address(b._1, b._2, b._3)
println(compareProduct(addrA, addrB))
}
}
def compareProduct(a: Product, b: Product): Boolean = {
a.productIterator.zip(b.productIterator).forall {
case (aVal, bVal) => aVal == bVal || aVal == null || bVal == null
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment