如何 "inject" 特征到基类型 类 以在具有特征的泛型类型方法中使用它们

How to "inject" traits to base type classes to use them in generic type methods with traits

好吧,也许我处理整件事的方式不对,但我需要帮助,我找不到解决方案。 我正在为自己在 Scala 中的使用打包一个 Redis 客户端实现。 所以我想要一个名为 RedisListClient[T] 的 class,它将在内部处理所有事情,而我只有一个 List[T] 可以使用。

现在我有一个工厂通过以下方式创建这样的客户端:

def createRedisListClient[T <: ByteStringFormattable](name: String): RedisListClient[T] = {
  new RedisListClient[T](name)
}

其中泛型类型必须包含特征 ByteStringFormattable。这是必需的,因为 Redis 客户端必须在内部将对象序列化为 ByteString,并且必须能够向后执行。

所需的特质就像

一样简单
trait ByteStringFormattable {
  type T
  val formatter: ByteStringFormatter[T]
}

所以我为自定义 class 搞定了这一切。假设一个 ScoreModel,可以保存分数。

class ScoreModel(userId: String, score: Long, scoreText: Option[String] = None) extends ByteStringFormattable {
  override type T = ScoreModel

  override val formatter: ByteStringFormatter[T] = new ByteStringFormatter[T] {
    override def serialize(data: T): ByteString = ByteString(s"$userId#$score#$scoreText")

    override def deserialize(bs: ByteString): T = {
      val split = bs.utf8String.split("#", 2)
      new ScoreModel(split(0), split(1).toLong, split.lift(2).map(s => Some(s)).getOrElse(None))
    }
  }
}

现在对我来说真正重要的问题是,我将如何使用 createRedisListClient[String]("myName")? 我的意思是 String 没有实现特征 ByteStringFormattable.

我试过像这样使用隐式转换:

implicit def fromString(s: String): SerializableString = new SerializableString(s)
implicit def toString(sws: SerializableString): String = sws.get

implicit class SerializableString(string: String) extends ByteStringFormattable {
  override type T = String

  val get = string

  override val formatter: ByteStringFormatter[T] = new ByteStringFormatter[String] {
    override def serialize(data: String): ByteString = ByteString(data.getBytes("UTF8"))

    override def deserialize(bs: ByteString): String = bs.utf8String
  }
}

但这无济于事。 Scala 编译器看到该函数调用的类型 String,检查它是否使用特征,然后失败。

那么,我是否有可能将函数调用与 StringInt 和 Co 等基本类型一起用于一些隐式的东西,或者我真的必须构建 Wrapper class是否像上例中的 SerializableString

我没有时间为你写完整的代码,因为你有所有这些 Redis 依赖项,所以重现对我来说很乏味,但让我大致解释一下。

你的方法

def createRedisListClient[T <: ByteStringFormattable](name: String)

只能接受扩展 ByteStringFormattable 的内容。如果您希望它能够接受类型 T,其中隐式转换 T -> ByteStringFormattable 在范围内可用,您必须这样说:

def createRedisListClient[T](name: String)(implicit conv: T => ByteStringFormattable)

您也可以使用 view bounds and context bounds 等语法糖来实现(视图边界已被弃用,因此我会避免使用它们)。

更笼统地说,您的问题的标准解决方案是 type classes. Let me also point you at my blog post on this topic。我写它是因为我觉得我们需要一篇文章将所有这些东西粘合在一起,从你必须解决的问题到隐式转换,view/context 边界和类型 类。如果你有15-20分钟的空闲时间,我想这对你会有帮助。

你真的想这样调用你的方法吗:

createRedisListClient[String]("myName")

即类型参数是 String 对你来说重要吗?正如另一个答案中提到的,由于您的类型限制,这无法工作,您必须将其更改为上下文绑定。

但这真的是您想要的,还是您实际上并不关心类型参数是什么而只想将 String 隐式转换为合适的类型?

如果您这样称呼它,则可以应用隐式转换:

createRedisListClient("myName")

不过,您必须删除隐式转换方法。这些方法是为隐式 class 生成的。 (实际上,生成这些方法是唯一声明 class 隐含的方法)。

从您所拥有的代码到工作代码的最短更改是使用 视图绑定 而不是您现在拥有的绑定。视图绑定会断言存在从 TByteStringFormattable 的某种隐式转换,这涵盖了 T 是子 class 的情况,但也涵盖了您拥有的情况String.

但是,在我们这样做之前,我认为需要进行一些重新安排。具体来说,我认为您对 ByteStringFormattable 的定义有问题,因为要转换的类型与 ByteStringFormattable 之间没有任何联系。照原样,现在你可以写这个,它会编译,即使它是错误的:

class ScoreModel(userId: String, score: Long, scoreText: Option[String] = None) extends ByteStringFormattable {
  override type T = String

  override val formatter: ByteStringFormatter[T] = new ByteStringFormatter[T] {
    override def serialize(data: String): ByteString = ByteString(data.getBytes("UTF8"))

    override def deserialize(bs: ByteString): String = bs.utf8String
  }
}

也就是说,现在您没有类型保证返回的 ByteStringFormatter 将是正确 class.

的格式化程序

因此,我将 ByteStringFormattable 重新定义为:

trait ByteStringFormattable[T] {
  val formatter: ByteStringFormatter[T]
}

现在,这是我写的一些东西,它编译了你想要的东西,并带有视图绑定。但是请继续阅读,因为在 Scala 2.11 中不推荐使用视图边界,所以我稍后会展示更好的、不推荐使用的方式(隐式参数)。

请注意,我必须调整您对 ByteString 方法的调用才能使其在此处进行编译,因为我周围没有 Redis 的 ByteString,并且不得不使用 com.google.protobuf.[=26 中的那个=]

class RedisListClient[T <% ByteStringFormattable[T]](val name: String) {

}

trait ByteStringFormatter[T] {
  def serialize(data: T): ByteString
  def deserialize(bs: ByteString): T
}

trait ByteStringFormattable[T] {
  val formatter: ByteStringFormatter[T]
}

class ScoreModel(userId: String, score: Long, scoreText: Option[String] = None) extends ByteStringFormattable[ScoreModel] {
  override val formatter: ByteStringFormatter[ScoreModel] = new ByteStringFormatter[ScoreModel] {
    override def serialize(data: ScoreModel): ByteString = ByteString.copyFromUtf8(s"$userId#$score#$scoreText")

    override def deserialize(bs: ByteString): ScoreModel = {
      val split = bs.toStringUtf8.split("#", 2)
      new ScoreModel(split(0), split(1).toLong, split.lift(2).map(s => Some(s)).getOrElse(None))
    }
  }
}


object RedisListClient {

  implicit class SerializableString(string: String) extends ByteStringFormattable[String] {
    val get = string

    override val formatter: ByteStringFormatter[String] = new ByteStringFormatter[String] {
      override def serialize(data: String): ByteString = ByteString.copyFrom(data.getBytes("UTF8"))

      override def deserialize(bs: ByteString): String = bs.toStringUtf8
    }
  }


  def createRedisListClient[T <% ByteStringFormattable[T]](name: String): RedisListClient[T] = {
    new RedisListClient[T](name)
  }

  def testIt() = {
    val x = createRedisListClient[ScoreModel]("ClientSM")
    val y = createRedisListClient[String]("Wat")
  }
}

可以编译,但正如我所说,它使用了已弃用的视图边界,在现代 Scala 中这些视图边界已被隐式参数取代。这是一种使用隐式参数执行所需操作的方法:

class RedisListClient[T](val name: String, conv: T => ByteStringFormattable[T]) {

}

trait ByteStringFormatter[T] {
  def serialize(data: T): ByteString
  def deserialize(bs: ByteString): T
}

trait ByteStringFormattable[T] {
  val formatter: ByteStringFormatter[T]
}

class ScoreModel(userId: String, score: Long, scoreText: Option[String] = None) extends ByteStringFormattable[ScoreModel] {
  override val formatter: ByteStringFormatter[ScoreModel] = new ByteStringFormatter[ScoreModel] {
    override def serialize(data: ScoreModel): ByteString = ByteString.copyFromUtf8(s"$userId#$score#$scoreText")

    override def deserialize(bs: ByteString): ScoreModel = {
      val split = bs.toStringUtf8.split("#", 2)
      new ScoreModel(split(0), split(1).toLong, split.lift(2).map(s => Some(s)).getOrElse(None))
    }
  }
}


object RedisListClient {

  implicit class SerializableString(string: String) extends ByteStringFormattable[String] {
    val get = string

    override val formatter: ByteStringFormatter[String] = new ByteStringFormatter[String] {
      override def serialize(data: String): ByteString = ByteString.copyFrom(data.getBytes("UTF8"))

      override def deserialize(bs: ByteString): String = bs.toStringUtf8
    }
  }


  def createRedisListClient[T](name: String)(implicit mkFormatter: T => ByteStringFormattable[T]): RedisListClient[T] = {
    new RedisListClient[T](name, mkFormatter)
  }

  def testIt() = {
    val x = createRedisListClient[ScoreModel]("ClientSM")
    val y = createRedisListClient[String]("Wat")
  }
}

这一切都很好,但是如果您已经要使用隐式参数,您可能想要的是隐式 ByteStringFormatter 对象,而不是必须在每个对象上调用 formatter元素。因此,让我们再修改一次,但使用隐式格式化程序对象,并且根本没有可格式化的特征:

class RedisListClient[T](val name: String, formatter: ByteStringFormatter[T]) {

}

trait ByteStringFormatter[T] {
  def serialize(data: T): ByteString
  def deserialize(bs: ByteString): T
}

class ScoreModel(userId: String, score: Long, scoreText: Option[String] = None) {
  def toBs: ByteString =  ByteString.copyFromUtf8(s"$userId#$score#$scoreText")
}


object RedisListClientImplicits {

  implicit val formatterSM: ByteStringFormatter[ScoreModel] = new ByteStringFormatter[ScoreModel] {
    override def serialize(data: ScoreModel): ByteString = data.toBs

    override def deserialize(bs: ByteString): ScoreModel = {
      val split = bs.toStringUtf8.split("#", 2)
      new ScoreModel(split(0), split(1).toLong, split.lift(2).map(s => Some(s)).getOrElse(None))
    }
  }

  implicit val formatterString: ByteStringFormatter[String] = new ByteStringFormatter[String] {
    override def serialize(data: String): ByteString = ByteString.copyFrom(data.getBytes("UTF8"))

    override def deserialize(bs: ByteString): String = bs.toStringUtf8
  }
}

object RedisListClient {
  def createRedisListClient[T](name: String)(implicit f: ByteStringFormatter[T]): RedisListClient[T] = {
    new RedisListClient[T](name, f)
  }
}

object RedisListClientTest {
  import RedisListClient._
  import RedisListClientImplicits._

  def testIt() = {
    val x = createRedisListClient[ScoreModel]("ClientSM")
    val y = createRedisListClient[String]("Wat")
  }
}