ojmb
ojmb

Reputation: 11

How to specify the return type of an abstract method to be the implementing class' type

abstract class TileStack {
  def foo = 1 + 1
  def extendRight: HashSet[_ <: TileStack]
}

class SquareTileStack extends TileStack {
  def extendRight = {
    def rec(currentStack: SquareTileStack = new SquareTileStack) = {
      // if(currentStack.isDoneRecursing) 
      // else rec(currentStack)
    }
    rec()
  }   
}

class TriangleTileStack extends TileStack {
  def extendRight = {
    def rec(currentStack: TriangleTileStack = new TriangleTileStack) = {
      // if(currentStack.isDoneRecursing) 
      // else rec(currentStack)
    }
    rec()
  }
}

object Tiler {
  def bar[T <: TileStack](stackList: HashSet[T]) = {
    var stackDictionary: HashSet[(T, HashSet[T])] = HashSet()
    for(stack: [T] <- stackList){
      hashOfHash += ((stack, stack.extendRight))
    }
  }
}

There are two problems: 1) This code won't compile because of the method Tiler.bar. This is because the stackDictionary is expecting a value of type HashSet[T] where T <: TileStack but is instead getting some random value _ <: TileStack. Realistically, T is actually being returned by extendRight, but this is not specified in the abstract class. What I would like to do is something like this:

abstract class TileStack {
  def foo = 1 + 1
  def extendRight: HashSet[this]
}

But that is obviously not allowed. What is the solution?

2) Notice that the actual implementations of extendRight are ugly. This is because extendRight is intended to be a recursive method that takes a default parameter of the current type, and builds it up as an extension of this. However, I could not figure out how to take a default parameter for an interface method without running into type issues.





Using the suggestion from som-snytt, this is the solution I came up with. I find it an incredibly odd construction, to use type parameters in this way, but it is what it is.

abstract class TileStack[Repr <: TileStack[Repr]] {
  def foo = 1 + 1
  def make(): TileStack[Repr]
  def extendRight(currentStack: TileStack[Repr] = make): HashSet[Repr]
}

class SquareTileStack extends TileStack[SquareTileStack] {
  def make = new SquareTileStack
  def extendRight(currentStack: TileStack[SquareTileStack] = make) = {
    new HashSet[SquareTileStack]
  }
}

class TriangleTileStack extends TileStack[TriangleTileStack] {
  def make = new TriangleTileStack
  def extendRight(currentStack: TileStack[TriangleTileStack] = make) = {
    new HashSet[TriangleTileStack]
  }
}

class Tiler {
  def bar[Repr <: TileStack[Repr]](stackList: HashSet[Repr]) = {
    var stackDictionary: HashSet[(Repr, HashSet[Repr])] = HashSet()
    for (stack <- stackList) {
      stackDictionary += ((stack, stack.extendRight()))
    }
  }
}

Upvotes: 1

Views: 102

Answers (2)

Alex Cruise
Alex Cruise

Reputation: 7979

You're looking for the MyType concept, which is not directly modeled in Scala. The most common way to encode it is with F-bounded polymorphism, which is also used under the covers in Java enums:

public abstract class Enum<E extends Enum<E>> {
    public final int compareTo(E o) { ... }
    public final Class<E> getDeclaringClass() { ... }
}

In Scala, you can also use bounded abstract type members, as Martin pointed out in the linked thread:

abstract class C {
  type MyType <: C

  def foo: MyType
}

case class D(x: Int) extends C {
  type MyType = D

  def foo = D(123)
}

Upvotes: 0

Randall Schulz
Randall Schulz

Reputation: 26486

In any type definition (class or trait) the type is available as this.type, but that is a path-dependent type and every instance of the type has a distinct this.type. The most common use of this.type is to signal to client code that a method returns the same instance on which it was invoked.

Upvotes: 2

Related Questions