gpt4 book ai didi

Scala:如何为任何案例类定义抽象的可复制父类(super class)?

转载 作者:行者123 更新时间:2023-12-04 11:20:41 28 4
gpt4 key购买 nike

请耐心等待,在 OP 有意义之前有一些上下文。我正在使用 Slick 3.1.x 和 Slick 代码生成器。 btw 整个源代码可以在 play-authenticate-usage-scala github project 中找到.对于这个项目,我想要一个光滑的通用 Dao,以避免为每个模型重复相同的样板代码。

我有一个 postgres sql 脚本,它在这里使用进化创建数据库:
1.sql

然后我调用一个生成器来生成以下数据模型:
Tables.scala

为了能够为模型类提供通用的 dao slick 实现,我需要它们遵守一些基本的抽象,例如

  • Entity特质:每个实体都有一个 id例如dao 需要 findById
  • AutoIncEntity trait 声明方法 def copyWithNewId(id : PK) : Entity[PK] .这是 dao 实现 createAndFetch 所需要的。持久化一个新实体并检索自动生成的 id一步PK。

  • copyWithNewId是OP的重点。请注意,它被称为 copyWithNewId而不是 copy避免无限递归。能够实现 GenericDaoAutoIncImpl允许插入并立即获取自动生成的 id ,实体行需要一个 copy(id = id)方法来自 <Model>Row在定义 GenericDaoAutoIncImpl 时的 case 类目前还不知道。相关实现如下:
    override def createAndFetch(entity: E): Future[Option[E]] = {
    val insertQuery = tableQuery returning tableQuery.map(_.id)
    into ((row, id) => row.copyWithNewId(id))
    db.run((insertQuery += entity).flatMap(row => findById(row.id)))
    }

    这需要我实现 copyWithNewId每个 AutoInc 中的方法 id生成的模型,这不好,例如
    // generated code and modified later to adapt it for the generic dao 
    case class UserRow(id: Long, ...) extends AutoIncEntity[Long] with Subject {
    override def copyWithNewId(id : Long) : Entity[Long] = this.copy(id = id)
    }

    但是,如果我可以 - 使用一些 Scala 技巧 - 定义我的 <Model>Row除了传递的 id 之外,可复制并复制自身的 Base 类的 case classes 子类即 IdCopyablecopy(id = id)那么我就不需要一遍又一遍地实现这个 copyWithNewId<Model>Row生成的案例类。

    有没有办法抽象或“上拉”重构 copy(id = id)对于任何包含 id 的案例类属性?有没有其他推荐的解决方案?

    更新 1 以下几乎总结了我遇到的问题:
    scala> abstract class BaseA[A <: BaseA[_]] { def copy(id : Int) : A }
    defined class BaseA

    scala> case class A(id: Int) extends BaseA[A]
    <console>:12: error: class A needs to be abstract, since method copy in class BaseA of type (id: Int)A is not defined
    case class A(id: Int) extends BaseA[A]
    ^

    scala> case class A(id: Int); val a = A(5); a.copy(6)
    defined class A
    a: A = A(5)
    res0: A = A(6)

    更新 2 使用下面建议的解决方案,我得到以下编译错误:
    [error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:26: could not find implicit value for parameter gen: shapeless.Generic.Aux[E,Repr]
    [error] val insertQuery = tableQuery returning tableQuery.map(_.id) into ((row, id) => row.copyWithNewId(id))
    [error] ^
    [error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:27: value id is not a member of insertQuery.SingleInsertResult
    [error] db.run((insertQuery += entity).flatMap(row => findById(row.id)))
    [error] ^
    [error] two errors found

    更新 3 使用和调整下面建议的镜头解决方案,我得到以下编译器错误:
    import shapeless._, tag.@@
    import shapeless._
    import tag.$at$at

    /**
    * Identifyable base for all Strong Entity Model types
    * @tparam PK Primary key type
    * @tparam E Actual case class EntityRow type
    */
    trait AutoIncEntity[PK, E <: AutoIncEntity[PK, E]] extends Entity[PK] { self: E =>
    //------------------------------------------------------------------------
    // public
    //------------------------------------------------------------------------
    /**
    * Returns the entity with updated id as generated by the database
    * @param id The entity id
    * @return the entity with updated id as generated by the database
    */
    def copyWithNewId(id : PK)(implicit mkLens: MkFieldLens.Aux[E, Symbol @@ Witness.`"id"`.T, PK]) : E = {
    (lens[E] >> 'id).set(self)(id)
    }
    }

    然后我收到以下编译器错误:
    [error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:26: could not find implicit value for parameter mkLens: shapeless.MkFieldLens.Aux[E,shapeless.tag.@@[Symbol,String("id")],PK]
    [error] val insertQuery = tableQuery returning tableQuery.map(_.id) into ((row, id) => row.copyWithNewId(id))
    [error] ^
    [error] /home/bravegag/code/play-authenticate-usage-scala/app/dao/GenericDaoAutoIncImpl.scala:27: value id is not a member of insertQuery.SingleInsertResult
    [error] db.run((insertQuery += entity).flatMap(row => findById(row.id)))
    [error] ^

    最佳答案

    shapeless您可以抽象案例类。

    1.手动抽象案例类

    如果您假设每个 idLong并且是 case 类的第一个参数,它可能如下所示:

    scala> import shapeless._, ops.hlist.{IsHCons, Prepend}
    import shapeless._
    import ops.hlist.{IsHCons, Prepend}

    scala> trait Copy[A <: Copy[A]] { self: A =>
    | def copyWithId[Repr <: HList, Tail <: HList](l: Long)(
    | implicit
    | gen: Generic.Aux[A,Repr],
    | cons: IsHCons.Aux[Repr,Long,Tail],
    | prep: Prepend.Aux[Long :: HNil,Tail,Repr]
    | ) = gen.from(prep(l :: HNil, cons.tail(gen.to(self))))
    | }
    defined trait Copy

    scala> case class Foo(id: Long, s: String) extends Copy[Foo]
    defined class Foo

    scala> Foo(4L, "foo").copyWithId(5L)
    res1: Foo = Foo(5,foo)

    也可能以更清洁的方式实现;我还不是很精通无形编程。而且我很确定也可以为具有任何类型 id 的案例类执行此操作。在参数列表中的任何位置。 见下文第 2 段。

    您可能希望将此逻辑封装在可重用的类型类中:
    scala> :paste
    // Entering paste mode (ctrl-D to finish)

    import shapeless._, ops.hlist.{IsHCons, Prepend}

    sealed trait IdCopy[A] {
    def copyWithId(self: A, id: Long): A
    }

    object IdCopy {
    def apply[A: IdCopy] = implicitly[IdCopy[A]]
    implicit def mkIdCopy[A, Repr <: HList, Tail <: HList](
    implicit
    gen: Generic.Aux[A,Repr],
    cons: IsHCons.Aux[Repr,Long,Tail],
    prep: Prepend.Aux[Long :: HNil,Tail,Repr]
    ): IdCopy[A] =
    new IdCopy[A] {
    def copyWithId(self: A, id: Long): A =
    gen.from(prep(id :: HNil, cons.tail(gen.to(self))))
    }
    }

    // Exiting paste mode, now interpreting.

    import shapeless._
    import ops.hlist.{IsHCons, Prepend}
    defined trait IdCopy
    defined object IdCopy

    scala> def copy[A: IdCopy](a: A, id: Long) = IdCopy[A].copyWithId(a, id)
    copy: [A](a: A, id: Long)(implicit evidence$1: IdCopy[A])A

    scala> case class Foo(id: Long, str: String)
    defined class Foo

    scala> copy(Foo(4L, "foo"), 5L)
    res0: Foo = Foo(5,foo)

    如果这对您很重要,您仍然可以将 copyWithId 方法放在您的案例类可以扩展的特征中:
    scala> trait Copy[A <: Copy[A]] { self: A =>
    | def copyWithId(id: Long)(implicit copy: IdCopy[A]) = copy.copyWithId(self, id)
    | }
    defined trait Copy

    scala> case class Foo(id: Long, str: String) extends Copy[Foo]
    defined class Foo

    scala> Foo(4L, "foo").copyWithId(5L)
    res1: Foo = Foo(5,foo)

    重要的是通过使用上下文边界或隐式参数将类型类实例从使用站点传播到需要它的地方。
    override def createAndFetch(entity: E)(implicit copy: IdCopy[E]): Future[Option[E]] = {
    val insertQuery = tableQuery returning tableQuery.map(_.id)
    into ((row, id) => row.copyWithId(id))
    db.run((insertQuery += entity).flatMap(row => findById(row.id)))
    }

    2. 使用镜头

    Shapeless 还提供 lenses您可以将其用于此目的。这样你就可以更新 id具有一些 id 的任何案例类的字段 field 。
    scala> :paste
    // Entering paste mode (ctrl-D to finish)

    sealed trait IdCopy[A,ID] {
    def copyWithId(self: A, id: ID): A
    }

    object IdCopy {
    import shapeless._, tag.@@
    implicit def mkIdCopy[A, ID](
    implicit
    mkLens: MkFieldLens.Aux[A, Symbol @@ Witness.`"id"`.T, ID]
    ): IdCopy[A,ID] =
    new IdCopy[A,ID] {
    def copyWithId(self: A, id: ID): A =
    (lens[A] >> 'id).set(self)(id)
    }
    }


    def copyWithId[ID, A](a: A, elem: ID)(implicit copy: IdCopy[A,ID]) = copy.copyWithId(a, elem)

    // Exiting paste mode, now interpreting.

    defined trait IdCopy
    defined object IdCopy
    copyWithId: [ID, A](a: A, elem: ID)(implicit copy: IdCopy[A,ID])A

    scala> trait Entity[ID] { def id: ID }
    defined trait Entity

    scala> case class Foo(id: String) extends Entity[String]
    defined class Foo

    scala> def assignNewIds[ID, A <: Entity[ID]](entities: List[A], ids: List[ID])(implicit copy: IdCopy[A,ID]): List[A] =
    | entities.zip(ids).map{ case (entity, id) => copyWithId(entity, id) }
    assignNewIds: [ID, A <: Entity[ID]](entities: List[A], ids: List[ID])(implicit copy: IdCopy[A,ID])List[A]

    scala> assignNewIds( List(Foo("foo"),Foo("bar")), List("new1", "new2"))
    res0: List[Foo] = List(Foo(new1), Foo(new2))

    请注意方法 assignNewIds 中的方式哪里 copyWithId被使用,类型类的实例 IdCopy[A,ID]作为隐式参数请求。这是因为 copyWithId需要 IdCopy[A,ID] 的隐式实例使用时在范围内。您需要从使用站点传播隐式实例,在该站点使用具体类型,例如 Foo ,沿着调用链一直向下到达 copyWithId叫做。

    您可以将隐式参数视为方法的依赖项。如果方法具有类型 IdCopy[A,ID] 的隐式参数,您需要在调用它时满足该依赖项。通常,这也会对调用方法的方法产生相同的依赖性。

    关于Scala:如何为任何案例类定义抽象的可复制父类(super class)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40995639/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com