tags:

views:

147

answers:

3

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).

+5  A: 

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)
}
one-zero-zero-one
Hmm.. your solution is probably what "abstract types" was meant to simplify. I consider "type parametrization" just as bad/good as "abstract types" in this case. You agree?
olle kullberg
@olle: I never understood the point of abstract type members and I never use them in my own code. I think of them as unnecessary redundancy in the language. (Paul P recently expressed similar thoughts at mailing list.)
one-zero-zero-one
@one-zero-zero-one: You are correct, abstract types seem to be pure sugar. But I for one like them very much, since i think it is easier to think about a "type field" that you can override in the subclass, than the corresponding type parameter syntax.
olle kullberg
Abstract types are not just syntactical sugar. While you can often turn one the abstract type version into a generic one and vice versa, they are not equivalent: If you have Foo[X], every instantiation like Foo[Int] establishes a new type. If you have Foo{ type X }, there is only one Foo type, which happens to contain a abstract type variable.
Landei
+5  A: 

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) 
    // ... 
}
Alex Cruise
So the magic way would be to use "this.type" (from your link) but since I am returning a new instance (not this) then it is not possible. Later in the post Odersky recommends abstract types for this problem, so I guess that ends the discussion. Thanks.
olle kullberg
Yep, this.type is too narrow to act as MyType: it's the type of this *but no other instance of this's class*.
Alex Cruise
+5  A: 

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/

Landei
This gets my vote. Great trick!
one-zero-zero-one
Hi @Landei, I do not see the difference between this and the solution provided by @one-zero-zero-one. Why is the self:T => Part needed? When I take it away I get the same output.
olle kullberg
@olle: The behavior is the same as in one-zero-zero-one's solution, it's just easier to read. Without the self:T part, you can write `case class Cat(name: String, weight: Int) extends Animal[Int]`, which clearly shouldn't be allowed. For details see the link I provided.
Landei
@Landei Ok, I see.
olle kullberg