scala - 通用实体记录 scala - 引入无形状的 id 字段

标签 scala entity record shapeless hlist

问题: 使用 ID 字段描述记录(这使其成为实体) ID 字段需要自动生成,以便 记录(也称为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
  }
}

输出:

Book: Book(Programming in Scala,Odersky, Spoon, Venners,2008)

PBook: PersistentBook(15,Programming in Scala,Odersky, Spoon, Venners,2008)

GenBook: 15 :: Programming in Scala :: Odersky, Spoon, Venners :: 2008 :: HNil

到目前为止我得到的最接近的是injectFieldSimpleGeneric,但它返回一个HList,而不是B 目标是能够生成记录的 ID,以便我可以使用自行生成的 ID 插入它们 当我尝试扩展它以生成 B 时,HList 与 B 不兼容

最佳答案

这里有两个问题。首先,您没有向编译器提供任何证据证明 AReprBRepr 通过某些共享结构相关。您可以通过更改 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]

问题是,即使您已经向编译器提供了 AB 共享结构的证据,但您还没有告诉它如何选择 B B 不会出现在此处的显式参数中的任何位置,并且编译器不会枚举范围内的所有案例类,试图找到具有适当 LabelledGeneric 实例的案例类。

有两种方法可以解决此问题。一种是拥有某种类型的类,如下所示:

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

然后为每对案例类提供 HasEntity.Aux[Book, PercientBook] 等实例。另一种方法是重写您的 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)

在这里您仍然需要指定目标,但编译器将能够验证它是否是有效的匹配并将所需的映射组合在一起。

关于scala - 通用实体记录 scala - 引入无形状的 id 字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59733141/

相关文章:

kubernetes - Kubectl更改记录的默认行为

.net - 选择是否对 F# 中的小型 AST 使用可区分联合或记录类型

mysql - 需要替换MySQL中的HTML单引号实体

scala - 磁铁图案和重载方法

scala - 如何从 Scala 中的字符串中去除除数字以外的所有内容(快速一行)

scala - 如何对 Set[ValidatedNel[String, Double]] 求和?

c# - 无法将类型 'System.String' 转换为类型 'System.Object'。 LINQ to Entities 仅支持转换实体数据模型基元类型

c# - 如何创建一个临时表并在与 Entity Framework 的相同连接中使用它?

php - 想用一个MySQL表影响多个页面

scala - 无法解析 Scala 中流的符号 #::error