【问题标题】:Dynamically populate some attributes for a set of case classes为一组案例类动态填充一些属性
【发布时间】:2019-07-10 12:07:48
【问题描述】:
  • 我有一组模型类,这些模型的一个子集有两个属性,称为createdBymodifiedBy
  • 我只需要为具有这些属性的对象填充这些属性。目前我正在使用一些样板代码进行模式匹配。
  case class DataSourceInstanceRow(id: Int, value: String, createdBy: Option[String], modifiedBy: Option[String])
  case class FormDefinitionRow(id: Int, formData: String, createdBy: Option[String], modifiedBy: Option[String])
  case class DecisionTableDefinitionRow(id: Int, rows: Int, definitions: List[String], createdBy: Option[String], modifiedBy: Option[String])
  case class ReportDef(id: Int, reportType: Int, reportName: String)


  def populateLogs[T](t: T, user: String): T = {
    t match {
      case ds: DataSourceInstanceRow =>
        if(ds.id == -1) ds.copy(modifiedBy = Some(user), createdBy = Some(user)).asInstanceOf[T]
        else ds.copy(modifiedBy = Some(user)).asInstanceOf[T]
      case fd: FormDefinitionRow =>
        if(fd.id == -1) fd.copy(modifiedBy = Some(user), createdBy = Some(user)).asInstanceOf[T]
        else fd.copy(modifiedBy = Some(user)).asInstanceOf[T]
      case dtd: DecisionTableDefinitionRow =>
        if(dtd.id == -1) dtd.copy(modifiedBy = Some(user), createdBy = Some(user)).asInstanceOf[T]
        else dtd.copy(modifiedBy = Some(user)).asInstanceOf[T]
      case o => o
    }
  }

  • DataSourceInstanceRowFormDefinitionRowDecisiontableDefinitionRow 具有 modifiedBycreatedBy 属性。但不是ReportDef

如何使用 shapeless 创建一个抽象来从上述模式匹配中删除样板?

【问题讨论】:

  • 如果您尽量减少您的问题并提供一个可行的示例,您更有可能得到答案。
  • @TravisBrown 感谢您的建议。我只是简化了问题。请看一看。

标签: scala pattern-matching shapeless


【解决方案1】:

你可以用Shapeless的Updater做这种事情:

import shapeless.{ LabelledGeneric, HList, Witness }
import shapeless.labelled.{FieldType, field}
import shapeless.ops.record.Updater

type CreatedBy = Witness.`'createdBy`.T
type ModifiedBy = Witness.`'modifiedBy`.T

def populateLogs[T, R <: HList](t: T, user: String)(implicit
  gen: LabelledGeneric.Aux[T, R],
  cb: Updater.Aux[R, FieldType[CreatedBy, Option[String]], R] = null,
  mb: Updater.Aux[R, FieldType[ModifiedBy, Option[String]], R] = null
): T = (
  for {
    createdBy <- Option(cb)
    modifiedBy <- Option(mb)
  } yield gen.from(
    createdBy(modifiedBy(gen.to(t), field(Some(user))), field(Some(user)))
  )
).getOrElse(t)

然后:

scala> populateLogs(DataSourceInstanceRow(1, "abc", None, None), "foo")
res0: DataSourceInstanceRow = DataSourceInstanceRow(1,abc,Some(foo),Some(foo))

scala> populateLogs(ReportDef(1, 2, "abc"), "foo")
res1: ReportDef = ReportDef(1,2,abc)

此实现使用了一个技巧,即您可以将null 默认值放在隐式参数上,如果编译器找不到隐式参数,编译器将使用它。它很简单而且效果很好,但有些人讨厌它。更有原则的方法使用隐式优先级:

trait UpdateBoth[T] extends ((T, String) => T)

object UpdateBoth extends LowPriorityUpdateBothInstances {
  implicit def updateWithFields[T, R <: HList](implicit
    gen: LabelledGeneric.Aux[T, R],
    cb: Updater.Aux[R, FieldType[CreatedBy, Option[String]], R],
    mb: Updater.Aux[R, FieldType[ModifiedBy, Option[String]], R]
  ): UpdateBoth[T] = (t, user) =>
    gen.from(cb(mb(gen.to(t), field(Some(user))), field(Some(user))))
}

trait LowPriorityUpdateBothInstances {
  implicit def updateAny[T]: UpdateBoth[T] = (t, _) => t
}

def populateLogs[T](t: T, user: String)(implicit update: UpdateBoth[T]): T =
  update(t, user)

这将完全一样。

【讨论】:

  • 您能否解释一下或指向一个资源来解释Witness.'createdBy.T 发生了什么?
  • 我们需要一个类型级别的符号来选择成员名称,Shapeless 的Witness 提供了一个支持Witness.`'createdBy`.T 语法的宏。
猜你喜欢
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
  • 2020-04-19
  • 2015-06-27
  • 1970-01-01
  • 2012-03-05
  • 2018-11-20
  • 2013-09-09
相关资源
最近更新 更多