Создание расширенного типа из класса case с помощью Shapeless в Scala

У меня есть этот пример кода:

import java.util.UUID

import shapeless.LabelledGeneric
import shapeless.record._
import shapeless.syntax.singleton._

object LabelTest extends App {

  case class IncomingThing(name: String, age: Int)
  case class DatabaseIncomingThing(name: String, age: Int, id: UUID)

  val genIncoming = LabelledGeneric[IncomingThing]
  val genDatabase = LabelledGeneric[DatabaseIncomingThing]

  val thing = IncomingThing("John", 42)

  val structuralVersionOfIncomingThing = genIncoming.to(thing)

  val updated = genDatabase.from(structuralVersionOfIncomingThing + ('id ->> UUID.randomUUID()))

  println(updated) // DatabaseIncomingThing(John,42,a45081f2-4ed5-4d2b-8fd9-4d8986875ed7)

}

Что приятно, потому что мне не нужно писать шаблон, который копирует все поля от IncomingThing до DatabaseIncomingThing. Однако мне бы не хотелось поддерживать оба этих типа, поскольку между ними существует очень четкая взаимосвязь (у одного есть id, у другого - нет).

Есть ли способ создать тип из заданного класса case, добавив или удалив одно поле? Я представляю себе что-то вроде

type IncomingThing = withoutField[DatabaseIncomingThing]('id)

Или что-то в этом роде.


person Felix    schedule 28.09.2018    source источник


Ответы (1)


Вместо DatabaseIncomingThing

val updated: DatabaseIncomingThing = 
  genDatabase.from(structuralVersionOfIncomingThing + ('id ->> UUID.randomUUID()))

вы можете работать с сырым HList

val updated1: FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: FieldType[Witness.`'id`.T, UUID] :: HNil = 
  structuralVersionOfIncomingThing + ('id ->> UUID.randomUUID())
val updated2: FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: HNil = 
  updated1 - 'id

На уровне типа

implicitly[Remover.Aux[FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: FieldType[Witness.`'id`.T, UUID] :: HNil,
  Witness.`'id`.T,
  (UUID, FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: HNil)]]

implicitly[Updater.Aux[
  FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: HNil,
  FieldType[Witness.`'id`.T, UUID],
  FieldType[Witness.`'name`.T, String] :: FieldType[Witness.`'age`.T, Int] :: FieldType[Witness.`'id`.T, UUID] :: HNil]]

Вы можете создать свой типовой класс

trait WithoutField[A, K] {
  type Out <: HList
}

object WithoutField {
  type Aux[A, K, Out0 <: HList] = WithoutField[A, K] { type Out = Out0 }
  def instance[A, K, Out0 <: HList]: Aux[A, K, Out0] = new WithoutField[A, K] { type Out = Out0 }

  implicit def mkWithoutField[A, L <: HList, K, T, L1 <: HList](implicit
    labelledGeneric: LabelledGeneric.Aux[A, L],
    remover: Remover.Aux[L, K, (T, L1)]): Aux[A, K, L1] = instance
}

и использовать это

def foo[Out <: HList](implicit withoutField: WithoutField.Aux[DatabaseIncomingThing, Witness.`'id`.T, Out]) = {
  // now you can use type Out inside
  type IncomingThing = Out
  ???
}
person Dmytro Mitin    schedule 28.09.2018
comment
Да, я хочу что-то, что будет использоваться моими зависимостями, например Play, Circe, Slick, Scalacheck и т. Д. Думаю, это слишком высокий заказ. Мы с коллегами обсуждаем, как оставить 2 типа явными, чтобы не усложнять смысл кода. Мне просто очень любопытно, можно ли это сделать. - person Felix; 28.09.2018
comment
Если вы используете LabelledGeneric, у вас должен быть Shapeless среди ваших зависимостей. Достаточно. - person Dmytro Mitin; 28.09.2018
comment
Если вы хотите, чтобы класс case DatabaseIncomingThing создавался автоматически, вам понадобится макрос. - person Dmytro Mitin; 28.09.2018
comment
Я обновил свой ответ еще раз. Вы можете создать свой типовой класс WithoutField. - person Dmytro Mitin; 28.09.2018