Sasgorilla
Sasgorilla

Reputation: 3130

How can I access the companion object of a Scala class passed as a type param?

I have a case class with companion object:

object Taco extends Dinner[Taco] {
  def ingredientNames: Seq[String] = Seq("filling", "cheese", "sauce")
}

case class Taco(filling: Meat, cheese: Cheese, sauce: Sauce) 
extends Dinner

And another one:

object Cheeseburger extends Dinner[Cheeseburger] {
  def ingredientNames: Seq[String] = Seq("cheese", "bun", "condiments")
}

case class CheeseBurger(cheese: Cheese, bun: Bun, condiments: Seq[Condiment]) 
extends Dinner[Cheeseburger]

I need to get ingredient names for these dinners before actually creating any dinner instances:

def printMenu[D <: Dinner[D]]: String = ???

How can I access the companion object of a Dinner subclass?

Upvotes: 1

Views: 1259

Answers (2)

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

You probably want the following construction (inspired by GenericCompanion from the standard collection library):

type Condiment = String
type Meat = String
type Cheese = String
type Sauce = String
type Bun = String


trait Dinner[A] {
  def companion: DinnerCompanion[A]
}

trait DinnerCompanion[A] {
  def ingredientNames: Seq[String]
}

case class Taco(filling: Meat, cheese: Cheese, sauce: Sauce) 
extends Dinner[Taco] {
  def companion = Taco
}

implicit object Taco extends DinnerCompanion[Taco] {
  def ingredientNames: Seq[String] = Seq("filling", "cheese", "sauce")
}

case class CheeseBurger(cheese: Cheese, bun: Bun, condiments: Seq[Condiment]) 
extends Dinner[CheeseBurger] {
  def companion = CheeseBurger
}

implicit object CheeseBurger extends DinnerCompanion[CheeseBurger] {
  def ingredientNames: Seq[String] = Seq("cheese", "bun", "condiments")
}

def printMenu[D: DinnerCompanion]: String = 
  implicitly[DinnerCompanion[D]].ingredientNames.mkString

Now every instance of a Dinner has method companion, and the companion in turn has ingredientNames.

EDIT added printMenu (has nothing to do with companion objects whatsoever, uses object Taco and object CheeseBurger as ordinary typeclass instances).

Upvotes: 1

Alexey Romanov
Alexey Romanov

Reputation: 170723

Type classes to the rescue:

trait Dinner { ... }
trait DinnerCompanion[A <: Dinner] {
  implicit def self: DinnerCompanion[A] = this
  def ingredientNames: Seq[String]
  ...
}

object Taco extends DinnerCompanion[Taco] {
  def ingredientNames: Seq[String] = Seq("filling", "cheese", "sauce")
}

case class Taco(filling: Meat, cheese: Cheese, sauce: Sauce) extends Dinner

def printMenu[A <: Dinner](implicit companion: DinnerCompanion[A]): String = 
  companion.ingredientNames.mkString(", ")

Upvotes: 4

Related Questions