Wątek przeniesiony 2022-12-08 12:16 z Java przez Riddle.

Działanie implicit conversion pod maską w Type Classach

0

Cześć,

Próbuję dogłębnie zrozumieć na poziomie kompilatora działanie implicit conversions w Scali 2 w użyciu w Type Classes, wykorzystując taki przykład z Programming Scala:

case class Address(street: String, city: String)
case class Person(name: String, address: Address)

trait ToJSON {
  def toJSON(level: Int = 0): String

  val INDENTATION = "  "
  def indentation(level: Int = 0): (String, String) = (INDENTATION * level, INDENTATION * (level + 1))
}

object JsonInstances {
  implicit class AddressToJSON(address: Address) extends ToJSON {
    def toJSON(level: Int = 0): String = {
      val (outdent, indent) = indentation(level)
      s"""{
         |${indent}"street": "${address.street}",
         |${indent}"city":"${address.city}"
         |$outdent}""".stripMargin
    }
  }

  implicit class PersonToJSON(person: Person) extends ToJSON {
    def toJSON(level: Int = 0): String = {
      val (outdent, indent) = indentation(level)
      s"""{
         |${indent}"name": "${person.name}",
         |${indent}"address": ${person.address.toJSON(level + 1)}
         |$outdent}""".stripMargin
    }
  }
}

object TypeClassApplication {
  def main(args: Array[String]): Unit = {
    val a = Address("1 Scala Lane", "Anytown")
    val p = Person("Buck Trends", a)
    println(a.toJSON())
    println()
    println(p.toJSON())
  }
}

Output:

{
  "street": "1 Scala Lane",
  "city":"Anytown"
}

{
  "name": "Buck Trends",
  "address": {
    "street": "1 Scala Lane",
    "city":"Anytown"
  }
}

W tym przykładzie chodzi o rozszerzenie case class Address i Person i dorzucenie metod konwertujących obiekt Address i Person na JSON zapisanego jako String.

Z tego co rozumiem, to implicit conversions działają w tym przypadku tak? (Poprawcie mnie, jeśli coś źle tu rozumiem)

  • Kompilator widząc wywołanie metody toJSON() na obiekcie a.toJSON() widzi, że ta metoda zwraca String, więc szuka konwersji implicit w lokalnym scope lub od razu w globalnym.
  • Dzięki importowi import JsonInstances._, kompilator widzi te dwie konwersje implicit jako klasy implicit i próbuje dopasować typy do tych metod.
  • Widzi, że klasa implicit AddressToJSON ma pojedynczy parameter address: Address, czyli typ na którym jest wywołana metoda a.toJSON() zgadza się, oraz widzi też metodę toJSON(), że zwraca typ String, więc jest dopasowanie i wywołuje już ta konkretną implementację toJSON() w klasie implicit AddressToJSON.
  • To samo dzieje się dla klasy Person i wywołania p.toJSON()

Pytanie jest takie:

  • Sprawdziłem jak wygląda bytecode po skompilowaniu za pomocą narzedzia javap, ale widzę, że klasa Address nie ma w swoim bytecode konkretnej metody toJSON() z klasy AddressToJSON
  • Co w takim razie dorzuca kompilator, że mogę wywołać metodę toJSON() na obiekcie case class Address czy Person? Czy obiekt case classy ma w takim przypadku adres do metody toJSON() na Metaspace i może wywołać te instrukcje? Chodzi mi bardziej o to co się dzieje na poziomie Java Heap / Metaspace.

Z góry dzięki!

3

To działa troszkę inaczej, klasa Address nie będzie miała tej metody w bytecode, bo jest zamieniana przy pomocy implicit conversion na AddressToJSON i z tej klasy jest wywoływana
zapis:

  implicit class AddressToJSON(address: Address) {
    def toJSON(level: Int = 0): String = ???
  }

jest przez kompilator zamieniany na coś typu:

  class AddressToJSON(address: Address) {
    def toJSON(level: Int = 0): String = ???
  }
  
  implicit def addressToJSON(address: Address): AddressToJSON = new AddressToJSON(address)

0 użytkowników online, w tym zalogowanych: 0, gości: 0