Scala:如何为任何案例类定义抽象可复制超类?

2024-01-09

请耐心等待,在 OP 有意义之前,有一些背景。我正在使用 Slick 3.1.x 和 slick 代码生成器。顺便说一句,整个源代码可以在play-authenticate-usage-scala github 项目 https://github.com/bravegag/play-authenticate-usage-scala。对于这个项目,我希望有一个灵活的通用 Dao 以避免为每个模型重复相同的样板代码。

我有一个 postgres sql 脚本,它使用演化创建数据库:1.sql https://github.com/bravegag/play-authenticate-usage-scala/blob/master/conf/evolutions/default/1.sql

然后,我调用一个生成器来生成以下数据模型:表.scala https://github.com/bravegag/play-authenticate-usage-scala/blob/master/app/generated/Tables.scala

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

  • Entity https://github.com/bravegag/play-authenticate-usage-scala/blob/master/app/generated/TablesAdapter.scala#L7特征:每个实体都有一个id例如dao 需要的findById
  • 汽车公司实体 https://github.com/bravegag/play-authenticate-usage-scala/blob/master/app/generated/TablesAdapter.scala#L18特征声明方法def copyWithNewId(id : PK) : Entity[PK]。这是 dao 实现所需要的createAndFetch保留一个新实体并检索自动生成的id一步PK。

This copyWithNewId是OP的重点。请注意,它被称为copyWithNewId并不是copy以避免无限递归。为了能够实施GenericDaoAutoIncImpl https://github.com/bravegag/play-authenticate-usage-scala/blob/master/app/dao/GenericDaoAutoIncImpl.scala#L14允许插入并立即获取自动生成的id,实体行需要一个copy(id = id)方法来自于<Model>Row案例类在定义时GenericDaoAutoIncImpl目前尚不清楚。相关实现如下:

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案例类 基类的子类,可复制并复制自身,除了传递的idi.e. IdCopyable with copy(id = id)那么我就不需要一遍又一遍地实施这个copyWithNewId对于每一个<Model>Row生成的案例类。

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

UPDATE 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)

UPDATE 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

UPDATE 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]                                                                ^

With 无形的 https://github.com/milessabin/shapeless您可以对案例类进行抽象。

1. 手动抽象案例类

如果你假设每个id is a Long并且是案例类的第一个参数,它可能如下所示:

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)

It might also be possible in a cleaner way; I'm not very proficient at shapeless programming yet. And I'm pretty sure it's also possible to do it for case classes with any type of id in any position in the parameter list. See paragraph 2 below.


您可能希望将此逻辑封装在可重用的类型类中:

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. 使用镜头

无形还提供lenses https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#boilerplate-free-lenses-for-arbitrary-case-classes您可以将其用于此目的。这样您就可以更新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 where copyWithId使用类型类的实例IdCopy[A,ID]作为隐式参数请求。这是因为copyWithId需要一个隐式实例IdCopy[A,ID]使用时要在范围内。您需要从使用站点传播隐式实例,您可以在其中使用具体类型,例如Foo,沿着调用链一直到哪里copyWithId叫做。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Scala:如何为任何案例类定义抽象可复制超类? 的相关文章

随机推荐

  • Pandas 在多列上应用元组解包函数

    Given a function它接受多个参数并返回多个值 如下所示 def tuple unpack value another value does some interesting stuff return value another
  • 动态更改 angular2-query-builder 中的配置

    在这里 我尝试根据 Angular2 query builder 中的字段值更改运算符 我的 TS 文件 Component selector my app template div h2 Hello name h2 div
  • Swift 中的全局修饰键按下检测

    我正在尝试使用Carbon的功能RegisterEventHotKey创建按下命令键时的热键 我这样使用它 InstallEventHandler GetApplicationEventTarget handler 1 eventType
  • perl awk OR sed,在两个时间戳之间搜索

    我有一个包含以下示例文本的文件 实际文字很大 2014 05 08 19 15 44 544824
  • WPF CheckBox 的选中和未选中状态的单独事件:为什么?

    有没有像这样的单一事件Changed我可以用它来一起处理这两个事件吗 为什么他们就这样分开呢 是否因为两个事件都有一个事件需要您按名称引用控件 您需要在 XAML 中指定该名称 这会增加混乱吗 不直接 但是 您可以对两者使用相同的事件处理程
  • 与 macOS Sierra 中的 AppTranslocation 相关的错误

    macOS Sierra 中的 Gatekeeper 似乎有点挑剔 无论如何 它导致我几年前制作的一个小实用程序抛出一个相当烦人的错误 由于我还没有时间深入研究 Sierra 的幕后变化 我不确定如何修复该错误 有人有处理与 AppTran
  • 如何将 Angular 2 服务与 Ionic 2 一起使用?

    我是 Ionic 2 的新手 我在 Angular 2 文档中读到 该服务需要在引导应用程序时注入 但在阅读 Ionic 2 教程时看不到任何引导程序 非常感谢任何帮助 Ionic2 中没有使用 Bootstrap 仅使用 App 来声明您
  • 反编译apk,lambda问题

    我反编译后的apk 分别出现了 java文件 但是有些文件的名字中有这样的 AudioPlayerActivity Lambda 1 java 这个 Lambda 1 是什么 AudioPlayerActivity Lambda 1 jav
  • 如何使用结构和实现的生命周期来推断实现的适当生命周期?

    我该如何解决这个错误 当我在中使用 匿名生命周期 时 我究竟在告诉编译器什么 impl struct LineHandlerInfo lt a gt label a str match literal a str f fn str gt O
  • MISRA 11.3:从 int 转换为指针

    我有一个关于如何解决 MISRA 2004 11 3 违规问题的问题 代码如下 tm uint8 read tm uint8 data data 0 tm uint8 0x00003DD2 data 1 tm uint8 0x00003DD
  • 重试 Bash 命令并超时

    如何重试 bash 命令直到其状态正常或达到超时 我最好的镜头 我正在寻找更简单的东西 NEXT WAIT TIME 0 COMMAND STATUS 1 until COMMAND STATUS eq 0 NEXT WAIT TIME e
  • 在 Android 中以编程方式添加活动

    我想在 Android 中以编程方式添加多个活动 我可以将这些活动动态添加到清单中吗 Android 中还有其他解决方案吗 您不能动态添加新活动到您的应用程序或编辑清单文件 因为这会违反 Android 安全模型 清单文件的目的之一是开发人
  • 在 inno-setup 中作为安装后运行 netsh.exe

    在以下脚本中 我运行 netsh exe 以在 TCP 端口上启用 http GET 请求 但即使安装日志显示退出代码为 0 并且参数也是正确的 我没有看到端口已启用 有任何想法吗 Script generated by the Inno
  • 如何为 alpine linux 安装 msodbcsql17 驱动程序?

    我找到了一个github上的相关问题 https github com Microsoft msphpsql issues 300但它位于 msphpsql 存储库下 所以我不确定它是否适用于 alpine 的一般驱动程序 据说MS还不支持
  • Selenium/Webdriver 的 python 绑定中的 get_Text() 等效项是什么

    我想从 Selenium 1 迁移到 Selenium 2 我使用 python 绑定 但是我找不到任何get text 功能 eg selenium find elements by css selector locator get te
  • 空手道 UI:driver.location 方法引发错误:路径:$,实际:null

    我正在尝试使用方法 driver location 但结果出现错误 路径 实际 null 我的代码 功能 浏览器自动化 背景 配置驱动程序 type chromedriver showDriverLog true 场景 尝试登录github
  • 用Python对图像进行二维小波滤波

    我正在尝试在 python 中进行二维小波滤波 我发现了 PyWavelets 并且已经研究它有一段时间了 我正在尝试进行 4 个级别的转换 当我打印出来时 它给出了奇怪的输出 我不确定到底发生了什么 我尝试了一些方法 但这是最新的示例 t
  • 浏览器如何解析/渲染 CSS?

    他们使用什么样的图形框架 技术来理解和绘制 CSS 指令 我这么问是因为我有兴趣编写使用 CSS 作为布局机制的软件 由于 CSS 是一个相当大的规范 所以编写我自己的规范不是非常困难吗 CSS 渲染器 这就是为什么会有如此多的浏览器差异
  • Git - 切换分支(窗口)和未提交的更改

    我很难理解一些 git DCVS 概念 事情是这样的 我创建了一个 git 项目 并从 SVN 存储库导入它 我做了一些承诺 我想尝试一些东西 所以我创建了一个名为常量更新 我切换到常量更新分支 移动一些文件 删除其他文件并添加更多文件 我
  • Scala:如何为任何案例类定义抽象可复制超类?

    请耐心等待 在 OP 有意义之前 有一些背景 我正在使用 Slick 3 1 x 和 slick 代码生成器 顺便说一句 整个源代码可以在play authenticate usage scala github 项目 https githu