Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

generics

scala

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.

like image 720
gimo4000 Avatar asked May 09 '12 07:05

gimo4000


1 Answers

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)
}
like image 198
Frank Avatar answered Sep 22 '22 06:09

Frank