Вы можете делать такие вещи с Shapeless's 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)
Это будет работать точно так же.