Применение рекурсивной операции к классу case в Scala - PullRequest
1 голос
/ 02 мая 2019

Я пытаюсь найти элегантный способ сделать следующее.

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

case class Foo(fooName: String, bar: Bar, baz: Baz)
case class Bar(barName: String, baz: Baz)
case class Baz(bazName: String)

Я хочу иметь возможность изменить имя (вв этом случае Foo и его дочерние объекты каким-то образом), например, для добавления к ним префикса с некоторым заголовком.

Я мог бы определить класс типов, который служит этой цели:

trait Titled[T] {
  def titled(t: T)(title: String): T
}

Для Fooреализация будет делать это:

implicit val fooTitled: Titled[Foo] = new Titled[Foo] {
  def titled(foo: Foo)(title: String): Foo = foo.copy(fooName = title + fooName)
}

Могут быть другие реализации, такие как Esquired, которые суффиксят имя с «Esq».

Но это не обновляет заголовокзначения детей, bar и baz, которые мне бы хотелось.

В конечном итоге я хотел бы получить что-то вроде этого:

//import all the implicits needed
val foo = Foo(...)
val titledFoo = foo.title("Mr. ") // names are prefixed with Mr.
val andEsquired = foo.esquire // names are also suffixed with Esq

Теперь я искалонлайн, чтобы увидеть, возможно ли это сделать, и я видел бесформенную библиотеку, но я все еще не на том уровне знаний Scala, который я мог бы расшифровать и создать такую ​​«магию», если это вообще возможно.

Я не обязательно ищу полное решение(хотя я был бы признателен за это), но если бы кто-то мог указать мне правильное направление, показать мне некоторые примеры, учебные пособия или что-то подобное, я был бы очень благодарен.

1 Ответ

1 голос
/ 02 мая 2019

Попробуйте следующий тип класса.Я предполагаю, что все классы дел, которые должны иметь экземпляр класса типа, имеют первое поле типа String с префиксом.

  import shapeless.ops.hlist.IsHCons
  import shapeless.{::, Generic, HList, HNil}

  trait Titled[T] {
    def titled(t: T)(title: String): T
  }

  object Titled {
    //implicit def identity[A]: Titled[A] = new Titled[A] {
    //  override def titled(t: A)(title: String): A = t
    //}

    implicit def transform[A <: Product, L <: HList, H, T <: HList](implicit
      generic: Generic.Aux[A, L],
      isHCons: IsHCons.Aux[L, String, T],
      tailTitled: HListTitled[T]): Titled[A] = new Titled[A] {
      override def titled(t: A)(title: String): A = {
        val l = generic.to(t)
        val head = isHCons.head(l)
        val tail = isHCons.tail(l)
        val newHead = title + head
        val newTail = tailTitled.titled(tail)(title)
        val newL = isHCons.cons(newHead, newTail)
        generic.from(newL)
      }
    }
  }

  trait HListTitled[L <: HList] {
    def titled(t: L)(title: String): L
  }

  object HListTitled {
    implicit val hnil: HListTitled[HNil] = new HListTitled[HNil] {
      override def titled(t: HNil)(title: String): HNil = HNil
    }

    implicit def hcons[H, T <: HList](implicit
      headTitled: Titled[H],
      tailTitled: HListTitled[T]): HListTitled[H :: T] = new HListTitled[H :: T] {
      override def titled(t: H :: T)(title: String): H :: T = 
        headTitled.titled(t.head)(title) :: tailTitled.titled(t.tail)(title)
    }
  }

  implicit class TitledOps[T](t: T) {
    def titled(title: String)(implicit ttld: Titled[T]): T = ttld.titled(t)(title)
  }

  case class Foo(fooName: String, bar: Bar, baz: Baz)
  case class Bar(barName: String, baz: Baz)
  case class Baz(bazName: String)
  case class Foo1(fooName: String, bar: Bar, baz: Baz, i: Int)

  Foo("Johnson", Bar("Smith", Baz("Doe")), Baz("James")).titled("Mr. ")
  // Foo(Mr. Johnson,Bar(Mr. Smith,Baz(Mr. Doe)),Baz(Mr. James))
  Foo1("Johnson", Bar("Smith", Baz("Doe")), Baz("James"), 10).titled("Mr. ")
  // doesn't compile
  // if you want it to compile uncomment "identity"
...