3

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?

Community
  • 1
  • 1
Lawrence Wagerfield
  • 6,471
  • 5
  • 42
  • 84

1 Answers1

5

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.

gourlaysama
  • 11,240
  • 3
  • 44
  • 51
  • Great answer, thank you. "The abstract-type version is more verbose, but it works." - it looks like doing it this way is counteractive to what I'm trying to achieve :P I will continue using type parameters! – Lawrence Wagerfield Jun 14 '13 at 17:01
  • @LawrenceWagerfield yes, it usually is counteractive. It plays well with the cake-pattern though, that's could be one reason to use it in some cases. – gourlaysama Jun 14 '13 at 17:13