0

I have these two traits:

sealed trait DbValue {
  type R
  type T <: DbValue
  def content(): R
  def copy(newContent: R = content): Option[T]
  def toString(): String
}

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

and want to make a method/class that takes objects that implement both traits, however, the DbValue is implemented by the class, and the DbValueOps is implemented by the companion object.

Example:

case class Column[T <: DbValue with DbValueOps] (val name: String, val cells: Vector[Option[T]] = Vector(), val blank_allowed: Boolean = true) {}

I now want to make a column that is generic over a type that is implemented like this:

case class DbString private (val content: String) extends DbValue {
  type R = String
  type T = DbString
  def copy(newContent: String = content): Option[DbString] = DbString(newContent)
  override def toString(): String = content
}

object DbString extends DbValueOps {
  type R = 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] = DbString(newContent)
  def isValidContent(newContent: String): Boolean = !newContent.isEmpty
}

However, when I try to make such a Column, with: Column[DbString]("name") I get the error: type arguments [database.DbString] do not conform to method apply's type parameter bounds [T <: database.DbValue with database.DbValueOps]

Is there a good way to do this? Or how should I change my design?

Frederik Baetens
  • 781
  • 1
  • 9
  • 20

2 Answers2

2

Class X and its companion object X are completely not related from OOP point of view (except their members "can see" each other even if they are private). Type of class X is X, type of object X is X.type and these types are not related either.

Try

case class Column[T <: DbValue, U <: DbValueOps](...

Column[DbString, DbString.type]("name")
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Can I somehow enforce that the DbValueOps is a companion object to the DbValue? Because like that i could make T a DbString, and U a companion object associated with DbInt for example. Perhaps by changing the type signature of the traits? – Frederik Baetens Apr 20 '19 at 13:40
  • 1
    @FrederikBaetens https://stackoverflow.com/questions/9172775/get-companion-object-of-class-by-given-generic-type-scala https://stackoverflow.com/questions/39260122/scala-type-constraint-of-companion-type – Dmytro Mitin Apr 20 '19 at 14:03
0

This is how I ended up solving it:

The traits, one implemented by an object, and the ops trait is instantiated by what i believe is called a typeclass instance.

sealed trait DbValue[R, T <: DbValue[R, T]] {
  def content(): R
  def copy(newContent: R = content): Option[T]
  def toString(): String
}

sealed trait DbValueOps[R, T <: DbValue[R, T]] {
  def apply(newContent: R): Option[T]
  def fromString(newContent: String): Option[T]
  def isValidContent(newContent: R): Boolean
  def fromDbValue[U, V <: DbValue[U, V]](dbValue: V): Option[T] = fromString(dbValue.toString())
}

and then my column just takes a type class instance as a parameter (in this case an implicit one, but that's probably not a good thing in this scenario). The type system enforces that the typeclass object is associated with the DbValue object.

case class Column[R, T <: DbValue[R, T]] private (
  val name: String,
  val cells: Vector[Option[T]] = Vector(),
  val blankAllowed: Boolean = true,
  val defaultValue: Option[T] = None,
  )(implicit ops: DbValueOps[R, T]) extends ColumnStringOps {
Frederik Baetens
  • 781
  • 1
  • 9
  • 20