Generi c записей сущностей scala - ввести поле id с бесформенным - PullRequest
2 голосов
/ 14 января 2020

Проблема: Опишите запись с полем идентификатора (что делает его сущностью). Поле идентификатора необходимо будет сгенерировать автоматически, чтобы запись (также известная как A) + ID = сущность (также известная как B)

trait Record extends Product
trait Entity {
  type Id
}
case class Book(title: String, author: String, publication: Int)
case class PersistentBook(id: Long, title: String, author: String, publication: Int) extends Entity {
  type Id = Long
}

object PersistentRecords {

  def main(args: Array[String]): Unit = {
    val bookGen = Generic[Book]
    val persistentBookGen = Generic[PersistentBook]

    val scalaBook = Book("Programming in Scala", "Odersky, Spoon, Venners", 2008)


    val scalaBookHlist = bookGen.to(scalaBook)

    val persistentScalaBookHList = 15L :: scalaBookHlist
    val persistentScalaBookFromGeneric: PersistentBook = persistentBookGen.from(persistentScalaBookHList)
    println(s"Book: $scalaBook")
    println(s"PBook: $persistentScalaBookFromGeneric")

    val genHListScalaBook = injectFieldSimpleGeneric(scalaBook, 15L)
    println(s"GenBook: $genHListScalaBook")

    val persistedScalaBook = injectFieldGeneric(scalaBook, 16L)
    println(s"PersistedBook: $persistedScalaBook")
  }

  // OK
  def injectField[F](baseRecord: HList, field: F): HList =
    field :: baseRecord

  // OK
  def injectFieldSimpleGeneric[A, ARepr <: HList, F](baseRecord: A, field: F)(implicit aGen: LabelledGeneric.Aux[A, ARepr]): HList = {
    val baseHList = aGen.to(baseRecord)
    val compositeHList: HList = field :: baseHList
    compositeHList
  }


  def injectFieldGeneric[A, ARepr <: HList, B <: Entity, BRepr <: HList, F <: Entity#Id ](baseRecord: A, idField: F)(
    implicit aGen: LabelledGeneric.Aux[A, ARepr],
             bGen: LabelledGeneric.Aux[B, BRepr]): B = {
    val baseHList = aGen.to(baseRecord)
    val compositeHList  = idField :: baseHList
    bGen.from(compositeHList) //Type mismatch. Required BRepr, found F :: ARepr
  }
}

Вывод:

Книга: Книга (Программирование на Scala, Одерский, Ложка, Веннерс, 2008)

PBook: PersistentBook (15, Программирование на Scala, Одерский, Spoon, Venners, 2008)

GenBook: 15 :: Программирование на Scala :: Одерский, Spoon, Venners :: 2008 :: HNil

Вот так самый близкий, который я получил до сих пор, это injectFieldSimpleGeneri c, но он возвращает HList, а не B, цель состоит в том, чтобы иметь возможность генерировать идентификаторы для записей, чтобы я мог вставить их с самостоятельно сгенерированными идентификаторами, когда я пытаюсь расширить его до выдать B, HList для B несовместим

1 Ответ

3 голосов
/ 14 января 2020

Здесь есть две проблемы. Во-первых, вы не предоставили компилятору никаких доказательств того, что ARepr и BRepr связаны некоторой общей структурой. Вы можете сделать это, изменив ограничение bGen:

import shapeless._, shapeless.labelled.{FieldType, field}

trait Record extends Product
trait Entity { type Id }
case class Book(title: String, author: String, publication: Int)
case class PersistentBook(id: Long, title: String, author: String, publication: Int) extends
  Entity { type Id = Long }

def injectFieldGeneric[A, ARepr <: HList, B <: Entity, F <: B#Id](baseRecord: A, idField: F)(
  implicit aGen: LabelledGeneric.Aux[A, ARepr],
           bGen: LabelledGeneric.Aux[B, FieldType[Witness.`'id`.T, F] :: ARepr]
  ): B = {
    val baseHList = aGen.to(baseRecord)
    val compositeHList  = field[Witness.`'id`.T](idField) :: baseHList
    bGen.from(compositeHList)
  }

Это работает:

val bookGen = LabelledGeneric[Book]
val scalaBook = Book("Programming in Scala", "Odersky, Spoon, Venners", 2008)
val persistedScalaBook =
  injectFieldGeneric[Book, bookGen.Repr, PersistentBook, Long](scalaBook, 16L)

А затем:

scala> println(persistedScalaBook)
PersistentBook(16,Programming in Scala,Odersky, Spoon, Venners,2008)

К сожалению, вы определенно не понимаете Не нужно указывать все параметры типа каждый раз, когда вы вызываете этот метод, и компилятор не может их определить:

scala> val persistedScalaBook = injectFieldGeneric(scalaBook, 16L)
                                ^
       error: inferred type arguments [Book,Nothing,Nothing,Long] do not conform to method injectFieldGeneric's type parameter bounds [A,ARepr <: shapeless.HList,B <: Entity,F <: B#Id]
                                                   ^
       error: type mismatch;
        found   : Book
        required: A
                                                              ^
       error: type mismatch;
        found   : Long(16L)
        required: F
                                                  ^
       error: could not find implicit value for parameter aGen: shapeless.LabelledGeneric.Aux[A,ARepr]

Проблема в том, что даже если вы предоставили компилятору доказательства того, что 1016 * и B структура обмена, вы не сказали ей, как выбрать B. B здесь нигде не фигурирует в явных аргументах, и компилятор не собирается перечислять все классы case в области видимости, пытаясь найти один с соответствующим LabelledGeneric экземпляром.

Есть два способа Вы можете решить эту проблему. Можно было бы иметь некоторый класс типов, подобный этому:

trait HasEntity[A] { type E }
object HasEntity { type Aux[A, E0] = HasEntity[A] { type E = E0 } }

, а затем предоставлять экземпляры, подобные HasEntity.Aux[Book, PersistentBook], для каждой пары классов дел. Другой подход состоит в том, чтобы переписать ваш injectFieldGeneric, чтобы вы могли предоставить один параметр типа:

class PartiallyAppliedInject[B <: Entity] {
  type IdK = Witness.`'id`.T

  def apply[A, ARepr <: HList, F <: B#Id, BRepr <: HList](baseRecord: A, idField: F)(
    implicit aGen: LabelledGeneric.Aux[A, ARepr],
             bGen: LabelledGeneric.Aux[B, FieldType[IdK, F] :: ARepr]
    ): B = {
      val baseHList = aGen.to(baseRecord)
      val compositeHList  = field[IdK](idField) :: baseHList
      bGen.from(compositeHList)
    }
}

def injectFieldGeneric[B <: Entity]: PartiallyAppliedInject[B] =
  new PartiallyAppliedInject[B]

И затем:

scala> val persistedScalaBook = injectFieldGeneric[PersistentBook](scalaBook, 16L)
persistedScalaBook: PersistentBook = PersistentBook(16,Programming in Scala,Odersky, Spoon, Venners,2008)

Здесь вам все еще нужно указать цель , но компилятор сможет проверить, что это допустимое совпадение, и собрать необходимое отображение.

...