Подтвердить что ты не робот

Объедините два класса классов в Scala, но с глубоко вложенными типами, без шаблона объектива

Как и этот вопрос класса класса, но с завихрением:

У меня есть класс case, который имеет некоторые глубоко вложенные классы case как свойства. В качестве простого примера,

case class Foo(fooPropA:Option[String], fooPropB:Option[Int])
case class Bar(barPropA:String, barPropB:Int)
case class FooBar(name:Option[String], foo:Foo, optionFoo: Option[Foo], bar:Option[Bar])

Я хотел бы объединить два класса case FooBar вместе, принимая значения, которые существуют для ввода, и применяя их к существующему экземпляру, создавая обновленную версию:

val fb1 = FooBar(Some("one"), Foo(Some("propA"), None), Some(Foo(Some("propA"), Some(3))), Some(Bar("propA", 4)))
val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), Some(Foo(Some("baz"), None)), None)
val merged = fb1.merge(fb2)
//merged = FooBar(Some("one"), Foo(Some("updated"), Some(2)), Some(Foo(Some("baz"), Some(3))), Some(Bar("propA", 4)))

Я знаю, что могу использовать объектив для создания глубоко вложенных обновлений свойств; однако, я считаю, что для этого потребуется много кодовых табличек: мне нужен объектив для каждого свойства, а другой - объектив в родительском классе. Это похоже на то, что нужно поддерживать, даже если вы используете более сжатый подход к созданию объектива в shapeless.

Сложная часть - это элемент optionFoo: в этом случае оба элемента существуют с некоторым (значением). Однако я хотел бы объединить свойства внутренней опции, а не просто переписать fb1 с новыми значениями fb2.

Мне интересно, есть ли хороший подход к объединению этих двух значений вместе таким образом, который требует минимального кода. Чувство моего чувства говорит мне попробовать использовать метод unapply для класса case, чтобы вернуть кортеж, перебрать и комбинировать кортежи в новый кортеж, а затем применить кортеж к классу case.

Есть ли более эффективный способ сделать это?

4b9b3361

Ответ 1

Один простой способ решить эту проблему - подумать о вашей операции слияния как нечто вроде добавления, заданного правильным набором моноидных экземпляров. Вы можете увидеть мой ответ здесь для решения очень похожей проблемы, но теперь это решение стало еще проще благодаря усилия команды typelevel. Сначала для классов case:

case class Foo(fooPropA: Option[String], fooPropB: Option[Int])
case class Bar(barPropA: String, barPropB: Int)
case class FooBar(name: Option[String], foo: Foo, bar: Option[Bar])

Затем некоторый шаблон (который не понадобится в предстоящей версии 2.0 Shapeless):

import shapeless._

implicit def fooIso = Iso.hlist(Foo.apply _, Foo.unapply _)
implicit def barIso = Iso.hlist(Bar.apply _, Bar.unapply _)
implicit def fooBarIso = Iso.hlist(FooBar.apply _, FooBar.unapply _)

Я собираюсь немного обмануть для большей ясности и поместить "второй" моноидный экземпляр для Option в область видимости вместо использования тегов:

import scalaz._, Scalaz._
import shapeless.contrib.scalaz._

implicit def optionSecondMonoid[A] = new Monoid[Option[A]] {
  val zero = None
  def append(a: Option[A], b: => Option[A]) = b orElse a
}

И все готово:

scala> val fb1 = FooBar(Some("1"), Foo(Some("A"), None), Some(Bar("A", 4)))
fb1: FooBar = FooBar(Some(one),Foo(Some(propA),None),Some(Bar(propA,4)))

scala> val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), None)
fb2: FooBar = FooBar(None,Foo(Some(updated),Some(2)),None)

scala> fb1 |+| fb2
res0: FooBar = FooBar(Some(1),Foo(Some(updated),Some(2)),Some(Bar(A,4)))

См. мой предыдущий ответ для дополнительного обсуждения.

Ответ 2

Мой предыдущий ответ использовал Shapeless 1.2.4, Scalaz и shapeless-contrib, а Shapeless 1.2.4 и shapeless-contrib довольно устарели на этом этапе (более двух лет спустя), поэтому здесь обновленный ответ с использованием Shapeless 2.2. 5 и cats 0.3.0. Я предполагаю такую ​​конфигурацию сборки:

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  "com.chuusai" %% "shapeless" % "2.2.5",
  "org.spire-math" %% "cats" % "0.3.0"
)

Shapeless теперь включает класс типа ProductTypeClass, который мы можем использовать здесь. В конце концов проект Miles Sabin kittens (или что-то подобное), скорее всего, обеспечит такую ​​вещь для классов типов кошек (аналогично той роли, которая бесформенна -contrib играл для Scalaz), но пока просто использовать ProductTypeClass не так уж плохо:

import algebra.Monoid, cats.std.all._, shapeless._

object caseClassMonoids extends ProductTypeClassCompanion[Monoid] {
  object typeClass extends ProductTypeClass[Monoid] {
    def product[H, T <: HList](ch: Monoid[H], ct: Monoid[T]): Monoid[H :: T] =
      new Monoid[H :: T] {
        def empty: H :: T = ch.empty :: ct.empty
        def combine(x: H :: T, y: H :: T): H :: T =
         ch.combine(x.head, y.head) :: ct.combine(x.tail, y.tail)
      }

    val emptyProduct: Monoid[HNil] = new Monoid[HNil] {
      def empty: HNil = HNil
      def combine(x: HNil, y: HNil): HNil = HNil
    }

    def project[F, G](inst: => Monoid[G], to: F => G, from: G => F): Monoid[F] =
      new Monoid[F] {
        def empty: F = from(inst.empty)
        def combine(x: F, y: F): F = from(inst.combine(to(x), to(y)))
      }
  }
}

И затем:

import cats.syntax.semigroup._
import caseClassMonoids._

case class Foo(fooPropA: Option[String], fooPropB: Option[Int])
case class Bar(barPropA: String, barPropB: Int)
case class FooBar(name: Option[String], foo: Foo, bar: Option[Bar])

И наконец:

scala> val fb1 = FooBar(Some("1"), Foo(Some("A"), None), Some(Bar("A", 4)))
fb1: FooBar = FooBar(Some(1),Foo(Some(A),None),Some(Bar(A,4)))

scala> val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), None)
fb2: FooBar = FooBar(None,Foo(Some(updated),Some(2)),None)

scala> fb1 |+| fb2
res0: FooBar = FooBar(Some(1),Foo(Some(Aupdated),Some(2)),Some(Bar(A,4)))

Обратите внимание, что это объединяет значения внутри Some, что не совсем то, что задает этот вопрос, но упоминается OP в комментарии к моему другому ответу. Если вы хотите заменить поведение, вы можете определить соответствующий Monoid[Option[A]], как в моем другом ответе.

Ответ 3

Используя Kittens 1.0.0-M8, теперь мы можем получить Semigroup (я думал, этого достаточно для этого пример, но Monoid - это просто импорт) без шаблона:

import cats.implicits._
import cats.derived._, semigroup._, legacy._

case class Foo(fooPropA: Option[String], fooPropB: Option[Int])
case class Bar(barPropA: String, barPropB: Int)
case class FooBar(name: Option[String], foo: Foo, bar: Option[Bar])

val fb1 = FooBar(Some("1"), Foo(Some("A"), None), Some(Bar("A", 4)))

val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), None)
println(fb1 |+| fb2)

Урожайность:

FooBar(Some(1),Foo(Some(Aupdated),Some(2)),Some(Bar(A,4)))