scala generic method overriding
Asked Answered
C

5

21

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.

Charmian answered 7/1, 2011 at 15:9 Comment(0)
K
26
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.

Kellykellyann answered 7/1, 2011 at 15:31 Comment(1)
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 :-)Charmian
D
13

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
}
Doucet answered 7/1, 2011 at 20:6 Comment(0)
K
4

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.

Kellykellyann answered 7/1, 2011 at 15:29 Comment(0)
S
1

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
    }        
  }

}
Shipley answered 11/8, 2011 at 6:42 Comment(1)
I laughed hard at this "and together it is a little weird way to tell that Foo[T] is exactly same as T." xDIdeogram
N
0

You can parameterize Foo to accomplish some of the effect easily:

abstract class Foo[F <: Foo[F]] { def f: F }
class Food extends Foo[Food] { def f = this }  // Yay!
class Fool extends Foo[Food] { def f = new Food }  // Uh-oh...

If you want to rule out the second case, there's no straightforward way to do it with the current features in Scala.

Also, some of what you seem to want doesn't make sense if you give an actual implementation in Foo. If Foo promises to take any Foo but you give it a method that insists on only a Food, it will break if you you pass it a different subclass of Foo (e.g. Fool). So the compiler won't let you do that.

abstract class Foo { def bar(f: Foo) : Foo }
class Foot extends Foo { def bar(f: Foo) = this }   // Fine!
class Fool extends Foo { def bar(f: Fool) = this }   // No good!
Nadeau answered 7/1, 2011 at 15:40 Comment(2)
I think we can trust the programmer not to define Fool as a Foo[Food] unless that's what he really intends.Kellykellyann
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 :)Nadeau

© 2022 - 2024 — McMap. All rights reserved.