Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save odersky/56323c309a186cffe9af to your computer and use it in GitHub Desktop.
Save odersky/56323c309a186cffe9af to your computer and use it in GitHub Desktop.
A simpler way to returning the "current" type in Scala.
/** This is in reference to @tploecat's blog http://tpolecat.github.io/2015/04/29/f-bounds.html
* where he compares F-bounded polymorphism and type classes for implementing "MyType".
*
* Curiously, the in my mind obvious solution is missing: Use abstract types.
*
* A lot of this material, including an argument against F-bounded for the use-case
* is discussed in:
*
* Kim B. Bruce, Martin Odersky, Philip Wadler:
* A Statically Safe Alternative to Virtual Types. ECOOP 1998: 523-549
*/
trait Pet {
type This <: Pet
def name: String
def renamed(newName: String): This
}
case class Fish(name: String, age: Int) extends Pet {
type This = Fish
def renamed(newName: String): Fish = copy(name = newName)
}
case class Kitty(name: String, age: Int) extends Pet {
type This = Kitty
def renamed(newName: String): Kitty = copy(name = newName)
}
object Test {
def esquire[A <: Pet](a: A): a.This = a.renamed(a.name + ", Esq.")
val f: Fish = esquire(new Fish("bob", 22))
}
@nuttycom
Copy link

nuttycom commented May 6, 2015

The problem with this solution is that the following would be a valid Pet:

case class CatFish(name: String, age: Int) extends Pet {
  type This = Kitty
  def renamed(newName: String): Kitty = Kitty(newName, age)
}

In short, it has the same safety failure as the F-bounded type solution.

@rjean-gilles
Copy link

This seems easily fixed by changing the definition of This:

type This >: this.type <: Pet

I may have overlooked some other limitation , but at first sight ti seems to do the job just fine.

@greenrd
Copy link

greenrd commented Jun 24, 2015

Even with rjean-gilles' addition, you probably still want F-bounded polymorphism for type preservation (which is quite useful in immutable programming). Without it, This can vary between those two bounds - you want it to be the same as the target type of your method call when you call a method, if you want the method to return a value "of the same type".

@immediatus
Copy link

What about have more than one level of inheritance?

Something like:

class Kitty(val name: String, val age: Int) extends Pet {
  type This = Kitty
  def renamed(newName: String): Kitty = new Kitty(newName, age)
}

class Cat(name: String) extends Kitty(name, 2) {
  override type This = Cat
  override def renamed(newName: String): Cat = new Cat(newName)
}

error: overriding type This in class Kitty, which equals Kitty;

@accmltr
Copy link

accmltr commented Apr 3, 2024

What about have more than one level of inheritance?

Something like:

class Kitty(val name: String, val age: Int) extends Pet {
  type This = Kitty
  def renamed(newName: String): Kitty = new Kitty(newName, age)
}

class Cat(name: String) extends Kitty(name, 2) {
  override type This = Cat
  override def renamed(newName: String): Cat = new Cat(newName)
}

error: overriding type This in class Kitty, which equals Kitty;

I also want to know!

@accmltr
Copy link

accmltr commented Apr 5, 2024

Not the best, but you can override variables and methods with more specific types. Works for my situation:

trait Container {
  type T = Any
  val contents: Any
}

class Animal(val name: String)
class Fish(name: String) extends Animal(name)

class A extends Container {

  override val contents: Animal = new Animal("Animal")

}

class AA extends A {
  override val contents: Fish = new Fish("Fish")
}

class B extends Container {

  override val contents: Int = 3874

}

val a = new A
println(a.contents.name) // Animal

val b = new B
println(b.contents) // 3874

val aa = new AA
println(aa.contents.name) // Fish

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment