scala generic method overriding

23,646

Solution 1

abstract class Foo{
   type T <: Foo
   def bar1(f:T):Boolean
   def bar2(f:T):T
}

class FooImpl extends Foo{
   type T = FooImpl
   override def bar1(f:FooImpl) = true
   override def bar2(f:FooImpl) = f
}

In this version, different subclasses of Foo all share Foo as a superclass, but to hold the return value of bar2 (or the parameters to bar1 or bar2) in a setting where all you know about your object (let's say it's named obj) is that it's a Foo, you need to use the type obj.T as the type of the variable.

Solution 2

To make Ken Blum's second version a little bit nicer you can use self types:

abstract class Foo[T] { self:T =>
   def bar1(f:T):Boolean
   def bar2(f:T):T
}

class FooImpl extends Foo[FooImpl]{
   override def bar1(f:FooImpl) = true
   override def bar2(f:FooImpl) = f
}

Solution 3

T needs to be a type parameter on the Foo class that you inherit from, not on the methods themselves.

abstract class Foo[T <: Foo[T]]{
   def bar1(f:T):Boolean
   def bar2(f:T):T
}

class FooImpl extends Foo[FooImpl]{
   override def bar1(f:FooImpl) = true
   override def bar2(f:FooImpl) = f
}

Different subclasses of Foo don't actually have a common supertype in this version of the code, because they extend from different parameterizations of Foo. You can use parameterized methods that refer to Foo[T] when you need to work with the common supertype, but I tend to prefer the abstract type solution I posted in my other answer, becuase it doesn't leak the details of the generics to all of the other functions that have to deal with Foos.

Solution 4

Ideally you combine things mentioned above, i.e.

trait Foo[T <: Foo[T]] { self:T =>

"[T <: Foo[T]]" means T is subclass of Foo[T], AND "self:T =>" means that Foo[T] is subclass of T, and together it is a little weird way to tell that Foo[T] is exactly same as T.

Only with that I could make following code compile and work as intended:

trait Field[T <: Field[T]] { self:T =>

  def x2:T

  def +(that:T):T

  def *(n:BigInt) : T = {
    if(n == 1)
      this
    else if(n == 2)
      this.x2
    else if(n == 3)
      this + this.x2
    else {
      val p = (this * (n/2)).x2
      if (n%2==0)
        p
      else
        p + this
    }        
  }

}
Share:
23,646
Jannik Luyten
Author by

Jannik Luyten

Updated on July 09, 2022

Comments

  • Jannik Luyten
    Jannik Luyten almost 2 years

    I have an abstract class :

    abstract class Foo(...){
       def bar1(f : Foo) : Boolean
       def bar2(f : Foo) : Foo
    }
    

    multiple classes extend Foo and override the methods

    class FooImpl(...) extends Foo{
        override def bar1(f : Foo) : Boolean {
            ...
        }
        override def bar2(f : Foo) : Foo {
            ...
        }
    } 
    

    Is it possible, using generics (or something) to make the overriding methods have the parametertype of the subclass implementing it? Like this :

    class FooImpl(...) extends Foo{
        override def bar1(f : FooImpl) : Boolean {
            ...
        }
        override def bar2(f : FooImpl) : FooImpl {
            ...
        }
    }
    

    I was thinking something along the line of the following, but that didn't seem to work...

    abstract class Foo(...){
        def bar1[T <: Foo](f : T) : Boolean
        def bar2[T <: Foo](f : T) : T
    }
    
    class FooImpl(...) extends Foo{
        override def bar1[FooImpl](f : FooImpl) : Boolean {
           ...
        }
        override def bar2[FooImpl](f : FooImpl) : FooImpl{
           ...
        }
    }
    

    Any help is much appreciated!

    Thank you.

  • Ken Bloom
    Ken Bloom over 13 years
    I think we can trust the programmer not to define Fool as a Foo[Food] unless that's what he really intends.
  • Rex Kerr
    Rex Kerr over 13 years
    I think we can trust the programmer to not intend to define Fool as a Foo[Food], but mistakes happen. If mistakes didn't happen, we probably wouldn't need type-checking at all :)
  • Jannik Luyten
    Jannik Luyten over 13 years
    This made it possible what I intended to do. So by using "type T <: Foo", I create a type T that is either Foo or any of its subclasses, and I can then use that type within the class like any other type, anywhere I want. Either as a parameter, var type, return value, ... Correct? Thank you very much. On a sidenote; I'm pleasantly surprised on the rapidness of the responses to this post. Hats off :-)
  • Niks
    Niks almost 4 years
    I laughed hard at this "and together it is a little weird way to tell that Foo[T] is exactly same as T." xD