如何 "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,检查它是否使用特征,然后失败。
那么,我是否有可能将函数调用与 String
、Int
和 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 隐含的方法)。
从您所拥有的代码到工作代码的最短更改是使用 视图绑定 而不是您现在拥有的绑定。视图绑定会断言存在从 T
到 ByteStringFormattable
的某种隐式转换,这涵盖了 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")
}
}
好吧,也许我处理整件事的方式不对,但我需要帮助,我找不到解决方案。
我正在为自己在 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,检查它是否使用特征,然后失败。
那么,我是否有可能将函数调用与 String
、Int
和 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 隐含的方法)。
从您所拥有的代码到工作代码的最短更改是使用 视图绑定 而不是您现在拥有的绑定。视图绑定会断言存在从 T
到 ByteStringFormattable
的某种隐式转换,这涵盖了 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")
}
}