Scala: абстракция функции toString для case-классов - PullRequest
1 голос
/ 19 марта 2019

Предположим, у меня есть класс case, определенный следующим образом:

case class User(name: String, age: Int)

Я бы хотел переопределить его toString метод следующим образом:

 case class User(name: String, age: Int) {
    override def toString: String = 
      s"name = $name
        age = $age"

Так что если я запусту print(user.toString), я получаю:

name = nameOfUser
age = ageOfUser

Теперь у меня есть другой класс с именем Computer, и он определен как

case class Computer(make: String, RAM: int, clockSpeed: Double)

Я хотел бы напечатать один и тот же для всех его значений.Я хотел бы что-то вроде:

  make = makeOfComputer
  RAM = RAMOfComputer
  clockSpeed = speedOfComputer

Вместо того, чтобы копировать, вставлять и адаптировать вышеуказанную функцию toString к классу Computer, я бы предпочел абстрагировать это так, чтобы она могла использоваться любымcase case.

У меня есть несколько идей: я могу использовать

 CaseClassType.unapply(caseClassInstance).get.productIterator.toList

, чтобы получить значения класса case, и

 classOf[CaseClass].getDeclaredFields.map(_.getName)

, чтобы получить именаполя.Это означает, что я могу найти список всех значений в классе case, а также список всех имен полей, даже не зная фактической структуры класса case.

Когда у меня есть эти две коллекции, я могу рекурсивно просмотреть их, чтобы создать строку.Я думал, что что-то вроде приведенного ниже может работать, но, к сожалению, scala не позволяет классам case наследовать от других классов case.

case class StringifiableCaseClass(){
  override def toString: String =
     //get the values and the fieldnames and create a string

Все, что мне нужно сделать, это заставить классы case расширяться StringifiableCaseClass и они смогут создать правильную строку.

Причина, по которой я хочу использовать класс case в качестве базового типа, заключается в том, что я могу использовать

 classOf[this.type]...etc

и

  this.type.unapply(this)...etc

в StringifiableCaseClass.

Любые идеи о том, как я могу достичь того, что я имею, после того, как я не могу расширить классы случаев с другими классами случаев?

Ответы [ 2 ]

5 голосов
/ 19 марта 2019

Базовая реализация не должна быть классом дел.Просто сделайте это признаком, и тогда все ваши кейс-классы смогут его расширить.

 trait Stringification { self: Product => 
    override def toString() = 
      getClass.getDeclaredFields
      .zip(productIterator.toSeq)
      .map { case (a, b) => s"${a.getName}=$b" }
      .mkString("\n")
 }

, а затем

  case class Foo(foo: String, bar: Int) extends Stringification
  case class Bar(foo: Foo, bar: String) extends Stringification

... и т.д.

2 голосов
/ 19 марта 2019

Если я правильно понимаю ваш сценарий использования, вы можете использовать Cats по умолчанию Show type-class или, если вам нужно больше настраиваемого поведения, вы можете напрямую использовать общий деривацию с Shapeless для достижения того, что вам нужно.

Чтобы упростить сценарий Cat's Show, прочитайте здесь .

Как вы можете прочитать, он имеет синтаксис, который позволит вам сделать myObject.show для любого T, который имеет Show[T] в области видимости.

Если вы хотите более настраиваемое поведение, вы можете попробовать перейти непосредственно к Shapeless.Пример того, что вам нужно, можно найти в этом примере Show тип-класса , и вы можете увидеть, что он работает здесь .

С учетом этих классов дел:

  sealed trait Super
  case class Foo(i: Int, s: String) extends Super
  case class Bar(i: Int) extends Super
  case class BarRec(i: Int, rec: Super) extends Super

  sealed trait MutualA
  case class MutualA1(x: Int) extends MutualA
  case class MutualA2(b: MutualB) extends MutualA

  sealed trait MutualB
  case class MutualB1(x: Int) extends MutualB
  case class MutualB2(b: MutualA) extends MutualB

Будет напечатано:

Bar(i = 0)   
BarRec(i = 1, rec = Foo(i = 0, s = foo))   
MutualA2(b = MutualB2(b = MutualA1(x = 0)))
...