Общее преобразование между двумя классами дел одной формы - PullRequest
0 голосов
/ 30 апреля 2019

У меня есть куча классов дел, которые имеют идентичные по форме аналоги в других запечатанных чертах (каждая запечатанная черта используется для исчерпывающего сопоставления с образцом в типизированных поведениях Akka), и я хочу преобразовать одну версию в другую с наименьшим количеством шаблонов.

Черты выглядят примерно так:

object RoutingCommands {
  sealed trait Command
  final case class ProtocolMsg(name: String, id: Int) extends Command
}

object ProtocolCommands {
  sealed trait Command
  final case class ProtocolMsg(name: String, id: Int) extends Command
}

Я знаю, что могу сделать преобразование, используя shapeless.Generic, например:

val msg1 = ProtocolCommands.ProtocolMsg("foo", 1)
val msg2 = Generic[RoutingCommands.ProtocolMsg].from(
  Generic[ProtocolCommands.ProtocolMsg].to(msg1)
)

Но необходимость делать это для каждого преобразования - это больше, чем просто построение падежных классов вручную. В идеале мне нужен конвертер, который выводит приведенный выше код на основе двух типов, предоставляемых во время компиляции, таких как val msg2 = convert(msg1)

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

def convert[A,B](a: A): B = Generic[B].from(
  Generic[A].to(a)
)

но это приводит к:

Error:(55, 44) could not find implicit value for parameter gen: shapeless.Generic[B]

При копании кажется, что мне нужно использовать Generic.Aux, что приводит меня к:

def convert[A, B, HL <: HList](a: A)(
  implicit
  genA: Generic.Aux[A, HL],
  genB: Generic.Aux[B, HL]
) = genB.from(genA.to(a))

Который, когда вызывается с:

val msg3 = convert(msg2)

Результат:

Error:(61, 57) could not find implicit value for parameter genB: shapeless.Generic.Aux[B,HL]

Это понятно, поскольку нигде не определен тип возвращаемого значения. Однако я выясняю, как дать подсказку, что такое B, чтобы genB можно было получить неявно.

Ответы [ 2 ]

3 голосов
/ 30 апреля 2019

Вы можете использовать «частичное применение»

def convert[A, HL <: HList](a: A)(
  implicit
  genA: Generic.Aux[A, HL]
) = new Helper(a, genA)

class Helper[A, HL <: HList](a: A, genA: Generic.Aux[A, HL]) {
  def apply[B](implicit genB: Generic.Aux[B, HL]) = genB.from(genA.to(a))
}

val msg3 = convert(msg2).apply[ProtocolCommands.ProtocolMsg]

(лучше использовать "частичное приложение" из ответа @ Бена)

или создайте класс типов

trait Convert[A, B] {
  def apply(a: A): B
}

object Convert {
  implicit def mkConvert[A, B, HL <: HList](implicit
    genA: Generic.Aux[A, HL],
    genB: Generic.Aux[B, HL]
  ): Convert[A, B] = a => genB.from(genA.to(a))
}

implicit class ConvertOps[A](a: A) {
  def convert[B](implicit cnv: Convert[A, B]): B = cnv(a)
}

val msg3 = msg2.convert[ProtocolCommands.ProtocolMsg]

https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration "6.3 Изучение конкретного случая: миграция класса случая"

2 голосов
/ 30 апреля 2019

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

val msg3 = convert[ProtocolCommands.ProtocolMsg, RoutingCommands.ProtocolMsg, String :: Int :: HNil](msg2)

, но это, очевидно, лишает смысла использовать Shapeless.Компилятору нужно только явно указать тип возвращаемого значения, и он может вывести остальные, но Scala напрямую не поддерживает явное предоставление только подмножества аргументов типа.

Как уже упоминалось в предыдущем ответе, вы можете использовать частичное приложение, чтобы обойти это ограничение, используя шаблон "частично примененный".Это работает лучше всего, если вы используете класс, параметризованный для возвращаемого типа, который вам нужно указать, а не тип ввода:

def convert[B] = new ConvertPartiallyApplied[B]

class ConvertPartiallyApplied[B] {
  def apply[A, Repr](a: A)(implicit genA: Generic.Aux[A, Repr], genB: Generic.Aux[B, Repr]) = genB.from(genA.to(a))
}

, который затем можно использовать просто с

convert[RoutingCommands.ProtocolMsg](msg2)
...