将通用伴随对象传递给超级构造函数
Passing generic companion object to super constructor
我正在尝试构造一个 trait
和一个 abstract class
以按消息进行子类型化(在 Akka 游戏环境中),以便我可以轻松地将它们转换为 Json
。
到目前为止做了什么:
abstract class OutputMessage(val companion: OutputMessageCompanion[OutputMessage]) {
def toJson: JsValue = Json.toJson(this)(companion.fmt)
}
trait OutputMessageCompanion[OT] {
implicit val fmt: OFormat[OT]
}
问题是,当我尝试按如下方式实现上述接口时:
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage(NotifyTableChange)
object NotifyTableChange extends OutputMessageCompanion[NotifyTableChange] {
override implicit val fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
}
我从 Intellij 得到这个错误:
Type mismatch, expected: OutputMessageCompanion[OutputMessage], actual: NotifyTableChange.type
我对 Scala 泛型有点陌生 - 非常感谢您提供一些解释。
P.S 我愿意接受比上述解决方案更通用的解决方案。
目标是,在获取 OutputMessage
的任何子类型时 - 轻松将其转换为 Json
.
编译器说您的 companion
是在 OutputMessage
上定义为通用参数而不是某些特定的子类型。要解决这个问题,您需要使用一个称为 F-bound generic 的技巧。此外,我不喜欢在每条消息中将该伴随对象存储为 val
的想法(毕竟您不希望它序列化,是吗?)。将其定义为 def
恕我直言,这是更好的权衡。代码会像这样(同伴保持不变):
abstract class OutputMessage[M <: OutputMessage[M]]() {
self: M => // required to match Json.toJson signature
protected def companion: OutputMessageCompanion[M]
def toJson: JsValue = Json.toJson(this)(companion.fmt)
}
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {
override protected def companion: OutputMessageCompanion[NotifyTableChange] = NotifyTableChange
}
您可能还会看到用于实现相同方法的标准 Scala 集合。
但是如果你需要 companion
只是用 JSON 格式编码,你可以像这样去掉它:
abstract class OutputMessage[M <: OutputMessage[M]]() {
self: M => // required to match Json.toJson signature
implicit protected def fmt: OFormat[M]
def toJson: JsValue = Json.toJson(this)
}
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {
override implicit protected def fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
}
显然你也想从 JSON 解码,你仍然需要一个伴生对象。
回复评论
- Referring the companion through a def - means that is a "method", thus defined once for all the instances of the subtype (and doesn't gets serialized)?
你用 val
声明的所有东西都会得到一个存储在对象中的字段(class 的实例)。默认情况下,序列化程序会尝试序列化所有字段。通常有一些方法可以说明某些字段应该被忽略(比如一些 @IgnoreAnnotation
)。这也意味着您将在每个无缘无故使用内存的对象中多一个 pointer/reference,这对您来说可能是也可能不是问题。将其声明为 def
会获得一种方法,因此您可以只将一个对象存储在某些 "static" 地方,例如伴随对象,或者每次都按需构建它。
- I'm kinda new to Scala, and I've peeked up the habit to put the format inside the companion object, would you recommend/refer to some source, about how to decide where is best to put your methods?
Scala 是一种不寻常的语言,没有直接映射涵盖其他语言中 object
概念的所有用例。作为第一条经验法则,object
有两种主要用法:
你会在其他语言中使用 static
的东西,即静态方法、常量和静态变量的容器(尽管不鼓励使用变量,尤其是 Scala 中的静态变量)
单例模式的实现。
- By f-bound generic - do you mean the lower bound of the M being OutputMessage[M] (btw why is it ok using M twice in the same expr. ?)
遗憾的是,wiki 仅提供基本描述。 F-bounded 多态性的整个想法是能够以某种通用方式访问基 class 类型中子 class 的类型。通常 A <: B
约束意味着 A
应该是 B
的子类型。这里使用 M <: OutputMessage[M]
,这意味着 M
应该是 OutputMessage[M]
的子类型,只需声明子类型 class 即可轻松满足(还有其他非满足这一点的简单方法)如:
class Child extends OutputMessage[Child}
这种技巧允许您使用 M
作为方法中的参数或结果类型。
- I'm a bit puzzled about the self bit ...
最后,self
位是另一个必要的技巧,因为在这种特殊情况下,F 有界多态性是不够的。当特征用作 mix-in 时,它通常与 trait
一起使用。在这种情况下,您可能希望限制 class 可以混合特征的内容。并且在同一类型中,它允许您在混合中使用该基类型的方法 trait
.
我会说我的回答中的特定用法有点不合常规,但它具有相同的双重效果:
在编译 OutputMessage
时,编译器可以假设该类型也将以某种方式成为 M
的类型(无论 M
是什么)
编译任何子类型时,编译器确保满足约束 #1。例如它不会让你做
case class SomeChild(i: Int) extends OutputMessage[SomeChild]
// this will fail because passing SomeChild breaks the restriction of self:M
case class AnotherChild(i: Int) extends OutputMessage[SomeChild]
实际上因为我不得不使用 self:M
,你可能可以在这里删除 F-bounded 部分,只需要
abstract class OutputMessage[M]() {
self: M =>
...
}
但我会保留它以更好地传达意思。
正如 SergGr 已经回答的那样,您现在需要一种 F-Bounded 多态性来解决这个问题。
但是,对于这些情况,我认为 (注意这只是我的意见) 最好使用 Typeclasses。
在您的情况下,您只想为任何值提供 toJson
方法,只要它们具有 OFormat[T]
class.
的实例
您可以使用这段 (更简单的恕我直言) 代码来实现。
object syntax {
object json {
implicit class JsonOps[T](val t: T) extends AnyVal {
def toJson(implicit: fmt: OFormat[T]): JsVal = Json.toJson(t)(fmt)
}
}
}
final case class NotifyTableChange(tableStatus: BizTable)
object NotifyTableChange {
implicit val fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
}
import syntax.json._
val m = NotifyTableChange(tableStatus = ???)
val mJson = m.toJson // This works!
JsonOps
class 是一个 Implicit Class ,它将为范围内存在隐式 OFormat
实例的任何值提供 toJson
方法.
并且由于 NotifyTableChange
class 的 伴随对象 定义了这种隐式,它总是在范围内 - 有关 where does scala look for implicits in this link.[=36= 的更多信息]
此外,鉴于它是 Value Class,此扩展方法不需要在 runtime.
中进行任何实例化
Here,你可以找到关于 F-Bounded 与 Typeclasses 的更详细讨论.
我正在尝试构造一个 trait
和一个 abstract class
以按消息进行子类型化(在 Akka 游戏环境中),以便我可以轻松地将它们转换为 Json
。
到目前为止做了什么:
abstract class OutputMessage(val companion: OutputMessageCompanion[OutputMessage]) {
def toJson: JsValue = Json.toJson(this)(companion.fmt)
}
trait OutputMessageCompanion[OT] {
implicit val fmt: OFormat[OT]
}
问题是,当我尝试按如下方式实现上述接口时:
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage(NotifyTableChange)
object NotifyTableChange extends OutputMessageCompanion[NotifyTableChange] {
override implicit val fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
}
我从 Intellij 得到这个错误:
Type mismatch, expected: OutputMessageCompanion[OutputMessage], actual: NotifyTableChange.type
我对 Scala 泛型有点陌生 - 非常感谢您提供一些解释。
P.S 我愿意接受比上述解决方案更通用的解决方案。
目标是,在获取 OutputMessage
的任何子类型时 - 轻松将其转换为 Json
.
编译器说您的 companion
是在 OutputMessage
上定义为通用参数而不是某些特定的子类型。要解决这个问题,您需要使用一个称为 F-bound generic 的技巧。此外,我不喜欢在每条消息中将该伴随对象存储为 val
的想法(毕竟您不希望它序列化,是吗?)。将其定义为 def
恕我直言,这是更好的权衡。代码会像这样(同伴保持不变):
abstract class OutputMessage[M <: OutputMessage[M]]() {
self: M => // required to match Json.toJson signature
protected def companion: OutputMessageCompanion[M]
def toJson: JsValue = Json.toJson(this)(companion.fmt)
}
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {
override protected def companion: OutputMessageCompanion[NotifyTableChange] = NotifyTableChange
}
您可能还会看到用于实现相同方法的标准 Scala 集合。
但是如果你需要 companion
只是用 JSON 格式编码,你可以像这样去掉它:
abstract class OutputMessage[M <: OutputMessage[M]]() {
self: M => // required to match Json.toJson signature
implicit protected def fmt: OFormat[M]
def toJson: JsValue = Json.toJson(this)
}
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {
override implicit protected def fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
}
显然你也想从 JSON 解码,你仍然需要一个伴生对象。
回复评论
- Referring the companion through a def - means that is a "method", thus defined once for all the instances of the subtype (and doesn't gets serialized)?
你用 val
声明的所有东西都会得到一个存储在对象中的字段(class 的实例)。默认情况下,序列化程序会尝试序列化所有字段。通常有一些方法可以说明某些字段应该被忽略(比如一些 @IgnoreAnnotation
)。这也意味着您将在每个无缘无故使用内存的对象中多一个 pointer/reference,这对您来说可能是也可能不是问题。将其声明为 def
会获得一种方法,因此您可以只将一个对象存储在某些 "static" 地方,例如伴随对象,或者每次都按需构建它。
- I'm kinda new to Scala, and I've peeked up the habit to put the format inside the companion object, would you recommend/refer to some source, about how to decide where is best to put your methods?
Scala 是一种不寻常的语言,没有直接映射涵盖其他语言中 object
概念的所有用例。作为第一条经验法则,object
有两种主要用法:
你会在其他语言中使用
static
的东西,即静态方法、常量和静态变量的容器(尽管不鼓励使用变量,尤其是 Scala 中的静态变量)单例模式的实现。
- By f-bound generic - do you mean the lower bound of the M being OutputMessage[M] (btw why is it ok using M twice in the same expr. ?)
遗憾的是,wiki 仅提供基本描述。 F-bounded 多态性的整个想法是能够以某种通用方式访问基 class 类型中子 class 的类型。通常 A <: B
约束意味着 A
应该是 B
的子类型。这里使用 M <: OutputMessage[M]
,这意味着 M
应该是 OutputMessage[M]
的子类型,只需声明子类型 class 即可轻松满足(还有其他非满足这一点的简单方法)如:
class Child extends OutputMessage[Child}
这种技巧允许您使用 M
作为方法中的参数或结果类型。
- I'm a bit puzzled about the self bit ...
最后,self
位是另一个必要的技巧,因为在这种特殊情况下,F 有界多态性是不够的。当特征用作 mix-in 时,它通常与 trait
一起使用。在这种情况下,您可能希望限制 class 可以混合特征的内容。并且在同一类型中,它允许您在混合中使用该基类型的方法 trait
.
我会说我的回答中的特定用法有点不合常规,但它具有相同的双重效果:
在编译
OutputMessage
时,编译器可以假设该类型也将以某种方式成为M
的类型(无论M
是什么)编译任何子类型时,编译器确保满足约束 #1。例如它不会让你做
case class SomeChild(i: Int) extends OutputMessage[SomeChild]
// this will fail because passing SomeChild breaks the restriction of self:M
case class AnotherChild(i: Int) extends OutputMessage[SomeChild]
实际上因为我不得不使用 self:M
,你可能可以在这里删除 F-bounded 部分,只需要
abstract class OutputMessage[M]() {
self: M =>
...
}
但我会保留它以更好地传达意思。
正如 SergGr 已经回答的那样,您现在需要一种 F-Bounded 多态性来解决这个问题。
但是,对于这些情况,我认为 (注意这只是我的意见) 最好使用 Typeclasses。
在您的情况下,您只想为任何值提供 toJson
方法,只要它们具有 OFormat[T]
class.
的实例
您可以使用这段 (更简单的恕我直言) 代码来实现。
object syntax {
object json {
implicit class JsonOps[T](val t: T) extends AnyVal {
def toJson(implicit: fmt: OFormat[T]): JsVal = Json.toJson(t)(fmt)
}
}
}
final case class NotifyTableChange(tableStatus: BizTable)
object NotifyTableChange {
implicit val fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
}
import syntax.json._
val m = NotifyTableChange(tableStatus = ???)
val mJson = m.toJson // This works!
JsonOps
class 是一个 Implicit Class ,它将为范围内存在隐式 OFormat
实例的任何值提供 toJson
方法.
并且由于 NotifyTableChange
class 的 伴随对象 定义了这种隐式,它总是在范围内 - 有关 where does scala look for implicits in this link.[=36= 的更多信息]
此外,鉴于它是 Value Class,此扩展方法不需要在 runtime.
Here,你可以找到关于 F-Bounded 与 Typeclasses 的更详细讨论.