Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"MyType" problem: Do I have to use abstract types (or generics) in Scala to return the actual class?

Tags:

generics

scala

I am not sure if there is a better way of doing this:

trait Animal {
  val name: String
  val weight: Int

  type SubAnimal <: Animal

  def updateName(n: String) = returnMe(n, this.weight)
  def updateWeight(w: Int) = returnMe(this.name, w)
  // Abstract protected method
  protected def returnMe(n: String, w: Int): SubAnimal
}

case class Dog(name: String, weight: Int) extends Animal {
  type SubAnimal = Dog
  override def returnMe(n: String, w: Int): Dog = Dog("Dog: " + name, w)
}
case class Cat(name: String, weight: Int) extends Animal {
  type SubAnimal = Cat
  override def returnMe(n: String, w: Int): Cat = Cat("Cat: " + name, w)
}

val fido = Dog("Fido", 11)
println( fido )
val fido2 = fido.updateWeight(12)
println( fido2 )

When I run the code I get this output:

$ scala animal.scala 
Dog(Fido,11)
Dog(Dog: Fido,12)

I want to return the actual type of the animal in question after the updateName or updateWeight has been called (i.e. not Animal). I know that if I override updateName and updateWeight directly, then the correct type will be returned and I do not have to use the abstract type SubAnimal.

Is there some tricky way of escaping the abstract type for the special case where the value of the abstract type is the same as the subclass?

(This is know as the "MyType" problem).

like image 562
olle kullberg Avatar asked Oct 18 '10 18:10

olle kullberg


3 Answers

This should work:

trait Animal[T] {
  self:T =>

  val name: String
  val weight: Int

  def updateName(n: String): T = returnMe(n, this.weight)
  def updateWeight(w: Int): T = returnMe(this.name, w)
  // Abstract protected method
  protected def returnMe(n: String, w: Int): T
}

case class Dog(name: String, weight: Int) extends Animal[Dog] {
  override def returnMe(n: String, w: Int): Dog = Dog("Dog: " + name, w)
}

case class Cat(name: String, weight: Int) extends Animal[Cat] {
   override def returnMe(n: String, w: Int): Cat = Cat("Cat: " + name, w)
}

Something like case class Cat(name: String, weight: Int) extends Animal[Dog] gets rejected by the compiler. Code stolen adapted from http://oldfashionedsoftware.com/2009/12/10/self-help/

like image 150
Landei Avatar answered Nov 15 '22 23:11

Landei


Some recent discussion on this topic... What you're looking for is commonly referred to as "MyType", and the typical Scala/Java encoding for it uses recursive type parameters:

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { 
    public final int compareTo(E o) 
    // ... 
}
like image 31
Alex Cruise Avatar answered Nov 15 '22 21:11

Alex Cruise


Using type parametrization?

trait Animal[A <: Animal[A]] {
  val name: String
  val weight: Int

  def updateName(n: String) = returnMe(n, this.weight)
  def updateWeight(w: Int) = returnMe(this.name, w)
  // Abstract protected method
  protected def returnMe(n: String, w: Int): A
}

case class Dog(name: String, weight: Int) extends Animal[Dog] {
  override def returnMe(n: String, w: Int) = Dog("Dog: " + name, w)
}

case class Cat(name: String, weight: Int) extends Animal[Cat] {
  override def returnMe(n: String, w: Int) = Cat("Cat: " + name, w)
}
like image 42
one-zero-zero-one Avatar answered Nov 15 '22 22:11

one-zero-zero-one