scala generic method overriding

2020-02-23 06:34发布

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.

5条回答
啃猪蹄的小仙女
2楼-- · 2020-02-23 07:04

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.

查看更多
Fickle 薄情
3楼-- · 2020-02-23 07:12

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
}
查看更多
何必那么认真
4楼-- · 2020-02-23 07:20

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

}
查看更多
我只想做你的唯一
5楼-- · 2020-02-23 07:23
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.

查看更多
beautiful°
6楼-- · 2020-02-23 07:26

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!
查看更多
登录 后发表回答