Scala类型类可以具有抽象类型成员吗?

时间:2019-04-21 01:24:22

标签: scala generics types typeclass abstract-type

我有这个类型的课:

sealed trait DbValueOps[T <: DbValue] {
  type R
  def apply(newContent: R): Option[T]
  def fromString(newContent: String): Option[T]
  def isValidContent(newContent: R): Boolean
}

具有这种类型的类实例:

package object DbOps {
  implicit val dbStringOps: DbValueOps[DbString] = new DbValueOps[DbString] {
    type R = String
    def apply(newContent: String): Option[DbString] =
      isValidContent(newContent) match {
        case true => Some(new DbString(newContent))
        case false => None
      }
    def fromString(newContent: String): Option[DbString] = this(newContent)
    def isValidContent(newContent: String): Boolean = !newContent.isEmpty
  }
}

但是当尝试将类型类实例与类似dbStringOps.isValidContent(newContent)的东西(其中newcontent是字符串)一起使用时,我遇到类型不匹配的情况:

found   : newContent.type (with underlying type String)
required: database.DbOps.dbStringOps.R

我可以通过将R从抽象类型成员转换为类型参数来使其工作,但这很丑陋,因为在我编写类型类的实现时已经确定了R。

2 个答案:

答案 0 :(得分:7)

在该implicit val上添加类型注释。

implicit val dbStringOps: DbValueOps[DbString] { type R = String } = ...

使用此签名接收隐式参数。

def f()(implicit db: DbValueOps[DbString] { type R = String }) = ...

也可以这样写。

type AUX[A, T] = DbValueOps[A] { type R = T }

def f()(implicit db: AUX[DbString, String]) = ...

答案 1 :(得分:4)

恵砂川答案可以完美地解决您的问题,但是除非您真的希望将设计的中心放在DbValues上,否则我建议将隐式放在包装的值上(在这种情况下为String),因为您不需要提供R与String的统一。

  trait DbValue[T]
  case class DbString(s:String) extends DbValue[String]


  sealed trait DbOps[R]{
    type T <: DbValue[R]
    def apply(newContent: R): Option[T]
    def fromString(newContent: String): Option[T]
    def isValidContent(newContent: R): Boolean
  }

  object DbOps {
    val dbStringOps: DbOps[String] = new DbOps[String] {
      type T = DbString
      def apply(newContent: String): Option[DbString] =
        isValidContent(newContent) match {
          case true => Some(new DbString(newContent))
          case false => None
        }
      def fromString(newContent: String): Option[DbString] = this(newContent)
      def isValidContent(newContent: String): Boolean = !newContent.isEmpty
    }

    val newContent = "hello"
    dbStringOps.isValidContent(newContent)
  }

将类型参数添加到DbValue可能会更加冗长,但是它阻止您定义诸如DbValueOps[DbString] { type R = Int }之类的东西,而这些东西可能并不是您想要的。