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

时间:2017-07-02 20:35:48

标签: scala

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

/* 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。怎么办呢?

1 个答案:

答案 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)