通用实体记录 scala - 引入无形状的 id 字段
Generic entity records scala - introduce id field with shapeless
问题:
用 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,但它 returns 是一个 HList,而不是 B
objective 是为了能够为记录生成 ID,以便我可以使用自己生成的 ID 插入它们
当我尝试扩展它以生成 B 时,B 的 HList 不兼容
这里有两个问题。首先是您没有向编译器提供 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]
问题是,即使您已经向编译器提供了 A
和 B
共享结构的证据,您还没有告诉它如何选择 B
。 B
没有出现在此处显式参数中的任何地方,并且编译器不会枚举范围内的所有情况 classes 试图找到一个具有适当 LabelledGeneric
实例的情况。
有两种方法可以解决此问题。一种是像这样的 class 类型:
trait HasEntity[A] { type E }
object HasEntity { type Aux[A, E0] = HasEntity[A] { type E = E0 } }
然后为每对案例 classes 提供类似 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)
在这里您仍然需要指定目标,但编译器将能够验证它是否有效匹配并将所需的映射放在一起。
问题: 用 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,但它 returns 是一个 HList,而不是 B objective 是为了能够为记录生成 ID,以便我可以使用自己生成的 ID 插入它们 当我尝试扩展它以生成 B 时,B 的 HList 不兼容
这里有两个问题。首先是您没有向编译器提供 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]
问题是,即使您已经向编译器提供了 A
和 B
共享结构的证据,您还没有告诉它如何选择 B
。 B
没有出现在此处显式参数中的任何地方,并且编译器不会枚举范围内的所有情况 classes 试图找到一个具有适当 LabelledGeneric
实例的情况。
有两种方法可以解决此问题。一种是像这样的 class 类型:
trait HasEntity[A] { type E }
object HasEntity { type Aux[A, E0] = HasEntity[A] { type E = E0 } }
然后为每对案例 classes 提供类似 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)
在这里您仍然需要指定目标,但编译器将能够验证它是否有效匹配并将所需的映射放在一起。