Lawrence Wagerfield
Lawrence Wagerfield

Reputation: 6621

Can self typing be used with abstract types?

I'm trying to achieve F-bounded polymorphism without using generics. I also need to use self-typing as I will be referencing this and expecting it to be typed as the subtype.

trait MyTrait[T] { self: Self => // Compilation error: cannot reference 'Self'
   type Self <: MyTrait[T]

   def doSomethingWithSubtype() {
      ...
   }
}

I can achieve this quite easily using type parameters (i.e. generics), but would like to know if I'm missing something to make the above compile. Can you use abstract types in this way?

Similar questions:

These provide workarounds for similar problems, leading me to believe the above is impossible?

F-Bound Polymorphism with Abstract Types instead of Parameter Types?

F-bounded quantification through type member instead of type parameter?

Upvotes: 3

Views: 113

Answers (1)

gourlaysama
gourlaysama

Reputation: 11290

You can self-type to an abstract type, with one tricky limitation: it has to be defined outside of your trait, but still in scope in a way that allows implementations to implement it with some type. You can do that by wrapping the whole thing into a trait:

trait MyTraitSystem {
    type TraitImpl <: MyTrait

    trait MyTrait { self: TraitImpl =>
        def doSomething(t: TraitImpl): String
    }
}

// with an example implementation of the trait:

object MyImpl extends MyTraitSystem {
  case class TraitImpl(data: String) extends MyTrait {
    def doSomething(t: TraitImpl): String = t.data + " " + data
  }
}

This is equivalent to this version using a type parameter:

trait MyTrait[T <: MyTrait[_]] { self: T =>
  def doSomething(t: T): String
}

// with an example implementation of the trait:

case class TraitImpl(data: String) extends MyTrait[TraitImpl] {
  def doSomething(t: TraitImpl): String = t.data + " " + data
}

Apart from an import MyImpl._ for the abstract-type version, they can be used the same way:

scala> import MyImpl._
    import MyImpl._

scala> val a = TraitImpl("hello")
a: MyImpl.TraitImpl = TraitImpl(hello)

scala> val b = TraitImpl("world")
b: MyImpl.TraitImpl = TraitImpl(world)

scala> b.doSomething(a)
res0: String = hello world

The abstract-type version is more verbose, but it works. You also need to carry around a MyTraitSystem in any method/class/... that needs to use TraitImpl so as to provide the type:

object SomewhereElse {
  def doSomethingElse(s: MyTraitSystem)(t: s.TraitImpl) = 
    ??? // s.TraitImpl is the implementation type
}

Compared to the type parameter version:

object SomewhereElse {
  def doSomethingElse[T <: MyTrait[_]](t: MyTrait[T]) = 
    ??? // T is the implementation type
}

This is probably only one of several ways to do this, but I don't think any way can match the conciseness of the type-parameter-based version.

Upvotes: 5

Related Questions