简体   繁体   中英

generic trait taking a class and it's companion object as a type parameter

So I want to have a generic trait take as a type parameter a class with a companion object that inherits from a specific base class and refer to both the companion object and the class itself. So,

abstract class BaseModel[T] {
  def all: Seq[T]
}

case class Customer(email: String, password: String)

object Customer extends BaseModel[Customer]

// This trait is my issue
trait BaseCrud[T] { 
  def table[T](f: T => String): String = {
    T.all.map(f _).mkString("")
  }
}

object Controller with BaseCrud {
  def foo = table(_.email)
}

I had some solutions to that trait that were closer but I distilled it down so you can see what I am trying to do.

Thanks

UPDATE

So I went with solution from Frank below, but I did manage to solve my initial puzzle. Though, in this case the solution was a bit ugly I'll include it here for completeness sake.

abstract class BaseModel[T] {
  def all: Seq[T] = Seq()
}

case class Customer(email: String, password: String)

object Customer extends BaseModel[Customer]

trait BaseCrud[T, U <: BaseModel[T]] { 
  def table(f: T => String)(implicit obj: U): String = {
    obj.all.map(f(_)).mkString("")
  }
}

object Controller extends BaseCrud[Customer, Customer.type] {
  implicit val model = Customer
  def foo = table(_.email)
}

So the type parameters changed to BaseCrud and the implicit was added to BaseCrud.table and implemented in Controller.model. I also fixed all my typos. I found it interesting Customer.type seems to be the type of the companion object.

There's a bunch of problems in your code.. let's tackle it one after the other:

  • def table[T](... note that this T overwrites the original type parameter for the scope of the method. Not what you want really, so just omit it and make this def table(...
  • object Controller with BaseCrud { contains two more mistakes:
    1. it has to be extends not with . The latter is only used after you already extended from some base-class/trait.
    2. BaseCrud requires a type parameter that you have to specify here, so something like BaseCrud[Customer]
  • and finally, to answer your actual question: There is a huge difference between a type parameter T and a companion object . They are inherently different things, so you cannot access the companion object via T.something . Instead you need to provide the companion object in your trait in some other way, for example as an abstract field.

Here's a version of what I believe you want to do:

abstract class BaseModel[T] {
  def all: Seq[T]
}

case class Customer(email: String, password: String)

object Customer extends BaseModel[Customer] {
  def all = List[Customer]() // have to define this
}

trait BaseCrud[T] {
  val companion : BaseModel[T]
  def table(f: T => String): String = {
    companion.all.map(f).mkString("")
  }
}

object Controller extends BaseCrud[Customer] {
  val companion = Customer
  def foo = table(_.email)
}

I think, you can use scala self type in BaseCrud trait to specify a class it should be mixed in. See linked question for details.

trait BaseCrud[T] {
  self: BaseModel[T] =>
  def table(f: T => String): String =
    all.map(f).mkString("")
}

object Controller extends BaseModel[Customer] with BaseCrud[Customer]{
  def foo = table(_.email)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM