Scala:缺少隐式泛型参数 shapeless.LabelledGeneric.Aux[T,L],如何提供?

Scala: missing implicit generic parameter shapeless.LabelledGeneric.Aux[T,L], how to provide it?

我正在编写与 finagle-postgres 一起使用 DB 的代码,我有映射器,并且确定我想尽可能多地将代码的公共部分移动到通用映射器。

所以,例如,我有 Mapper

case class ProcessState(
  ...
)

class ProcessStateMapper(client: PostgresClient)
  extends EntityMapper[ProcessState]("process_state", client)(rowDecoder) {

  ...

  def create(state: ProcessState): Future[ProcessState] = {
    val fields = Updates(state)
    val columnNames = fields.updates.map(_._1).mkString(", ")
    val placeholders = (1 to fields.updates.size).map(i => s"$$$i").mkString(", ")
    for {
      inserted <- client.prepareAndExecute(
        s"INSERT INTO $tableName ($columnNames) VALUES ($placeholders)", fields.params: _*)
    } yield {
      require(inserted > 0, s"Failed to create $tableName: $state")
      state
    }
  }

}

此创建方法工作正常,Updates 需要隐式 shapeless.LabelledGeneric.Aux[T,L],并且在此上下文中找到它。

但是如果我将方法移至泛型 class,由于类型擦除,它无法找到隐式值...

abstract class EntityMapper[T <: Product](
  val tableName: String, val client: PostgresClient)(
  implicit val rowDecoder: RowDecoder[T]) {

  ...

  def create(state: T): Future[T] = {
    val fields = Updates(state)
    val columnNames = fields.updates.map(_._1).mkString(", ")
    val placeholders = (1 to fields.updates.size).map(i => s"$$$i").mkString(", ")
    for {
      inserted <- client.prepareAndExecute(
        s"INSERT INTO $tableName ($columnNames) VALUES ($placeholders)", fields.params: _*)
    } yield {
      require(inserted > 0, s"Failed to create $tableName: $state")
      state
    }
  }
}

所以这段代码没有编译错误

could not find implicit value for parameter gen: shapeless.LabelledGeneric.Aux[T,L]
    val fields = Updates(state)

所以我尝试提供这个参数

abstract class EntityMapper[T <: Product](
  val tableName: String, val client: PostgresClient)(
  implicit val rowDecoder: RowDecoder[T], val lgen: LabelledGeneric.Aux[T, _]) {
  ...
}

class ProcessStateMapper(client: PostgresClient)
  extends EntityMapper[ProcessState]("process_state", client)(rowDecoder, ProcessStateMapper.lgen) {
  ...
}

object ProcessStateMapper {
  ...

  val lgen: LabelledGeneric.Aux[ProcessState, _] = LabelledGeneric[ProcessState]
}

但是没有用,我尝试了一些其他的提供方式,但是都失败了。你知道这样做的正确方法是什么吗?

Updates#apply 需要的不仅仅是 LabelledGeneric.Aux[P, L]。 完整签名为:

def apply[P <: Product, L <: HList, MP <: HList](p: P)(implicit
    gen: LabelledGeneric.Aux[P, L],
    mapper: Mapper.Aux[toLabelledParam.type, L, MP],
    toList: ToList[MP, (String, Param[_])],
    columnNamer: ColumnNamer
  ): Updates

为了能够调用该函数,您还需要将所有这些隐式参数添加到您的 class 中。由于 LMP 类型参数也需要在范围内,因此您还需要将它们添加为 class 的类型参数。所以你最终得到这个:

abstract class EntityMapper[P <: Product, L <: HList, MP <: HList](
  val tableName: String, val client: PostgresClient)(
  implicit rowDecoder: RowDecoder[T],
           gen: LabelledGeneric.Aux[P, L],
           mapper: Mapper.Aux[toLabelledParam.type, L, MP],
           toList: ToList[MP, (String, Param[_])],
           columnNamer: ColumnNamer) {

这太臭了!不仅因为所有这些隐式,还因为现在 class.

中有两个完全没有意义的类型参数

但是您可以使用一些隐含的方法来解决这个问题:

trait Updatable[P <: Product] {
  def apply(p: P): Updates
}

object Updatable {
  implicit def instance[P <: Product, L <: HList, MP <: HList](
    implicit gen: LabelledGeneric.Aux[P, L],
             mapper: Mapper.Aux[toLabelledParam.type, L, MP],
             toList: ToList[MP, (String, Param[_])],
             columnNamer: ColumnNamer): Updatable[P] = new Updatable[P] {
    def apply(p: P): Updates = Updates(p)
  }
}

现在您可以这样写 EntityMapper

abstract class EntityMapper[T <: Product](
  val tableName: String,
  val client: PostgresClient)(
  implicit updatable: Updatable[T]) {
  …
  def create(state: T): Future[T] = {
    val fields = updatable(state)
    …
  }
  …
}