在某些特征中实现最终定义,其中类型参数可能相同,也可能不同



在我的问题之前的一些设置:

/* 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].
  • Data特征T的输入类型不必与_converter的输入类型相同。如果它们碰巧是同一类型,那么实现可能只是final def value(record: T): Option[NewType] = converter(record)。更棘手的情况是输入类型不同。假设要Data的输入类型是String,但要converter的输入类型是Long。这将如何处理?

看起来你已经完成了 90% 的实现类型类模式,所以我将尝试通过完成它来解决你的问题。这是关于它的很好的阅读。简而言之,您错过的是一个签名,该签名指出,如果在隐式作用域中可以找到转换器的一个(并且只有一个(实现,请使用它来运行转换(或由 trait 定义的任何其他内容(。

签名如下:

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方法,则会收到错误:

错误: 找不到参数转换器的隐式值: 新类型转换器[布尔值]

奖金

通过隐式为对象的已知功能提供证据非常普遍,以至于 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)

相关内容

最新更新