Frederik Baetens
Frederik Baetens

Reputation: 871

Can a scala type class have abstract type members?

I have this type class:

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

with this type class instance:

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
  }
}

But when trying to use the type class instance with something like dbStringOps.isValidContent(newContent) where newcontent is a string, i get a type mismatch:

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

I can get it to work by converting R from an abstract type member to a type parameter, but that is ugly since R is already determined when i'm writing the implementation of the type class.

Upvotes: 0

Views: 664

Answers (2)

GClaramunt
GClaramunt

Reputation: 3158

恵砂川 answer solves your problem perfectly, but unless you really want to center your design on DbValues, I will suggest to center your implicit on the wrapped value (String on this case), as you don't need to provide the unification of R with 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)
  }

Adding a type parameter to DbValue can be a little more verbose but it prevents you to define things like DbValueOps[DbString] { type R = Int } that probably is NOT what are you looking for.

Upvotes: 4

恵砂川
恵砂川

Reputation: 218

add type annotations on that implicit val.

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

or

receive implicit parameter using this signature.

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

can also write like this.

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

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

Upvotes: 7

Related Questions