我的问题之前的一些设置:
/* roughly equivalent to a union type */
sealed trait NewType
object NewType {
final case class Boolean(record: Boolean) extends NewType
final case class Long(record: Long) extends NewType
final case class String(record: String) extends NewType
}
/* Try to convert a record of type T to a NewType */
sealed trait NewTypeConverter[T] { def apply(record: T): Option[NewType] }
object NewTypeConverter {
trait BooleanConverter[T] extends NewTypeConverter[T] {
override def apply(record: T): Option[NewType.Boolean]
}
trait LongConverter[T] extends NewTypeConverter[T] {
override def apply(record: T): Option[NewType.Long]
}
trait StringConverter[T] extends NewTypeConverter[T] {
override def apply(record: T): Option[NewType.String]
}
}
我想定义一个特征Data
,如下所示:
trait Data[T] {
def name: String
def converter: NewTypeConverter[_]
final def value(record: T): Option[NewType] = ??? // calls converter
}
如何实施此final def value(record: T): Option[NewType]
?
有几点需要注意:
apply
converter
方法的返回类型必须与value
的返回类型相同。因此,如果您碰巧有BooleanConverter
,那么value
必须返回Option[NewValue.Boolean]
。T
特征的输入类型Data
不必与_
的输入类型converter
相同。如果它们恰好是同一类型,则实现可能只是final def value(record: T): Option[NewType] = converter(record)
。更棘手的情况是输入类型不同时。我们假设Data
的输入类型为String
,但converter
的输入类型为Long
。怎么办呢?答案 0 :(得分:1)
通过实施Type Class模式看起来你是90%,所以我会尝试通过完成它来解决你的问题。 Here is a nice reading about it.简而言之,您遗漏的是一个签名,表明如果在隐式作用域中找到转换器的一个(且仅一个)实现,则使用它来运行转换(或由其定义的任何其他内容)性状)。
签名如下:
final def value(record: T)(implicit c: NewTypeConverter[T]): Option[NewType]
鉴于这种严格的签名也使得实现非常简单:
final def value(record: T)(implicit c: NewTypeConverter[T]): Option[NewType] =
c(record) // literally only applies `c`, the converter
现在,只要您将转换器实例置于隐式作用域中,例如以下内容:
implicit val converter: NewTypeConverter[Boolean] =
new StringConverter[Boolean] {
override def apply(record: Boolean): Option[NewType.String] =
if (record) Some(NewType.String("TRUE"))
else Some(NewType.String("FALSE"))
}
您可以实例化trait
(示例中简化):
trait Data[T] {
def name: String
final def value(record: T)(implicit c: NewTypeConverter[T]): Option[NewType] =
c(record)
}
final case class BooleanData(name: String) extends Data[Boolean]
val bool = BooleanData(name = "foo")
并使用它:
println(bool.value(true)) // prints Some(String(TRUE))
println(bool.value(false)) // prints Some(String(FALSE))
如果您尝试从无法访问隐式实例的地方调用value
方法,则会收到错误:
错误:无法找到参数转换器的隐含值:NewTypeConverter [Boolean]
通过implicits提供对象的已知功能的证据非常普遍,如果您需要提供此类证据,Scala可以使用一些语法糖(例如,您有一种方法可以调用value
方法)但你不必直接使用它。它表示如下,:
正好在泛型类型之后:
def methodThatCallsValue[T: Data](convertee: T): Option[NewType] =
data.value(convertee)
它被称为上下文绑定并且等同于以下(在示例中明确完成):
def methodThatCallsValue(convertee: T)(implicit $ev: Data[T]): Option[NewType] =
data.value(convertee)