1
votes

I want to define a method in a Scala trait where both a parameter to the method and the return type correspond to the same concrete class which extends the trait. I've tried something like the following:

trait A {
  def foo(obj: this.type): this.type
}

final case class B(val bar: Int) extends A {
  override def foo(obj: B): B = {
    B(obj.bar + this.bar)
  }
}

object Main {
  def main(args: Array[String]) = {
    val b1 = new B(0)
    val b2 = new B(0)
    val b3: B = b1.foo(b2)
  }
}

However, trying to compile this code gives the following error:

Test.scala:5: error: class B needs to be abstract. Missing implementation for:
  def foo(obj: B.this.type): B.this.type // inherited from trait A
case class B(val bar: Int) extends A {
           ^
Test.scala:6: error: method foo overrides nothing.
Note: the super classes of class B contain the following, non final members named foo:
def foo: ((obj: _1.type): _1.type) forSome { val _1: B }
  override def foo(obj: B): B = {
               ^
2 errors

There's obviously something I'm misunderstanding about the Scala type system here. The signature of foo in class B is what I want it to be, but I don't know how to correctly define the method in A (or if this is even possible). It seems like this question is asking something quite similar, but I don't immediately see how the answer applies in my situation.

2
There's always only one inhabitant of this.type, and it's this. Your "override" says "I'll take a B and return some, maybe other, instance of B" which is not specific enough.Oleg Pyzhcov
@OlegPyzhcov I do want to return some, maybe other instance of B. I realized my example was probably unclear since I didn't give any idea of what the foo method might look like. I updated the example.Michael Mior

2 Answers

3
votes

The type annotation this.type means that you may only return this. So in that case you may not return another instance of B, the same holds for the method parameter.

If this was just about the return type, a solution would be to require foo to return something of type A, the override method in B can specialize the return type to return B.

However since you also have an argument which you want to be of the type of the subtype you could use a Self Recursive Type. The following example compiles and should do what you want.

  trait A[S <: A[S]] {
    def foo(obj: S): S
  }

  case class B(val bar: Int) extends A[B] {
    override def foo(obj: B): B = {
      B(obj.bar + 1)
    }
  }
3
votes

Consider type class solution

case class B(bar: Int)

// type class
trait Fooable[A] {
  def foo(x: A, y: A): A
}

// proof that B satisfies Fooable constraints
implicit val fooableB: Fooable[B] = new Fooable[B] {
  override def foo(x: B, y: B): B = B(x.bar + y.bar)
}

// a bit of syntax sugar to enable x foo y
implicit class FooableOps[A](x: A) {
  def foo(y: A)(implicit ev: Fooable[A]) = ev.foo(x,y)
}

val b1 = B(1)
val b2 = B(41)
b1.foo(b2)
// B(42)

which Scala 3 simplifies to

case class B(bar: Int)

// type class
trait Fooable[A] {
  extension (x: A) def foo (y: A): A
}

// proof that B satisfies Fooable constraints + syntactic sugar
given Fooable[B] with
   extension (x: B) def foo (y: B): B = B(x.bar + y.bar) 


val b1 = B(1)
val b2 = B(41)
b1.foo(b2)
// B(42)

See Scala FAQ: How can a method in a superclass return a value of the “current” type?