SIP suggestion: Add a This type to allow superclass method to return subclass type

104 views
Skip to first unread message

Jxtps

unread,
Apr 25, 2016, 2:17:03 PM4/25/16
to scala-debate
https://tpolecat.github.io/2015/04/29/f-bounds.html has a great discussion about:

A common question on the #scala IRC channel is

I have a type hierarchy … how do I declare a supertype method that returns the “current” type?

The aesthetics are however a bit challenging - either you use an F-bounded type, Pet[A <: Pet[A]], which can "leak", or you use a TypeClass, which leads to a lot of boilerplate for each specific class.

Suggestion: add the concept of a "This" type, which denotes the type of the runtime instance, as known at compile time.

Example:

class StringBuilder {
  def append(s:String):This = {
    //...
    this
  }
}

trait Fancy {
  self: StringBuilder =>
  def fancy(s:String):This = {
    //...
    append(s)
  }
}

class FancyStringBuilder extends StringBuilder with Fancy {
}

val f = new FancyStringBuilder()
f.append("hello").fancy("world!")

where returning This allows the last chained call to happen - normally f.append() would return StringBuilder, which knows nothing about fancy(), and we'd get an error.


The compiler would only use & enforce the type as known at compile time, so this doesn't work:

val f:StringBuilder = new FancyStringBuilder()
f.append("hello").fancy("world!") // => compilation error, StringBuilder doesn't have fancy()


Conceptually, the calls would be very similar to:

val f = new FancyStringBuilder()
f.append("hello").asInstanceOf[FancyStringBuilder].fancy("world!")

Where the cast is guaranteed to succeed thanks to appen() returning This.


A method returning This has to either return this, return the result of another method that returns This, or be in a final class and return an instance of that class.


So in tpolecat's first example, you'd be allowed to do (changes highlighted):
trait Pet {
  def name: String
  def renamed(newName: String): This
}

final case class Fish(name: String, age: Int) extends Pet {
  def renamed(newName: String): This = copy(name = newName)
}

While I'm only really envisioning the This type being used as a method return type, and being very strict in terms of what can be returned from such methods (i.e. pretty much only this), it might be both possible & desirable to expand the scope to include method parameters, and more lenient returns.

Allowing the This type to be used in e.g. collections, or getting passed along in type definitions (class Fish extends Pet[This]) sounds dangerous to me, but maybe that could be useful as well?

Thoughts?






Daniel Armak

unread,
Apr 25, 2016, 3:02:51 PM4/25/16
to Jxtps, scala-debate

I also feel it would be very useful to provide a more convenient syntax for F-bounds. Most importantly, traits with F-bounds wouldn't be needlessly generic anymore, spreading existential types everywhere. But to accomplish this, we really need to allow This everywhere the F-bound argument can be used today: in method argument types, field types, type arguments, etc. And why restrict it?

To maintain backward compatibility, it could be named something like this.subtype.

The spec seems simple:

  • In a type that uses this.subtype, it behaves as some unknown subtype of the current type.
  • In a type A that extends B, an expression defined in B which type this.subtype when viewed from A has some unknown subtype of A.

But the implementation would require new info in the compiled class.

-- 
Daniel Armak

Justin du coeur

unread,
Apr 25, 2016, 3:07:48 PM4/25/16
to Jxtps, scala-debate
+1 from the just-a-user viewpoint: I wind up needing F-bounds moderately often, and I *hate* the side-effects they wind up imposing on my code.  Having a solution that didn't introduce pointless genericity would be a big win for my codebase...

--
You received this message because you are subscribed to the Google Groups "scala-debate" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-debate...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alex Cruise

unread,
Apr 25, 2016, 6:22:13 PM4/25/16
to Justin du coeur, Jxtps, scala-debate
A discussion from the past that should provide some context to this MyType vs. F-bounded polymorphism debate:


-0xe1a

Daniel Armak

unread,
Apr 26, 2016, 5:25:21 AM4/26/16
to Alex Cruise, Justin du coeur, Jxtps, scala-debate

They discuss various alternatives, but in the end the OP settles for F-bounds. And we know from experience that F-bounds are useful and are often used, and that the introduced genericity (and verbosity) are harmful. A syntax deliberately equivalent to F-bounds would be simple to specify and to understand.

The problem is how to represent this in the Scala type system. F-bounds can’t be implemented with abstract type members instead of type arguments:

trait Tr { self =>
    type Self <: Tr { type Self <: self.Self }
}

We cannot say here self: Self as we would in the equivalent type-argument situation. So even if we enforce this at compile time, other users of the type wouldn’t know about it.

We could have the new syntax be equivalent to this:

trait Tr[F <: Tr[F]] { self: F =>
    def copy(): F
}
object Tr {
    type Trx = Tr[F] forSome{ type F <: Tr[F] }
}
import Tr.Trx

class C extends Tr[C] {
    override def copy(): C = new C
}

// Use site
val tr: Trx = new C
val copy: Trx = tr.copy()

This much can be generated with a macro annotation today, and may even be worth writing by hand when we use F-bounds. If Tr has extra generic arguments, then so will Trx.

C still has to extend Tr[C], but that’s not a big a problem IMO. Everyone else needs to use Tr.Trx and not Tr, which is annoying but less annoying than using Tr[_] (which would have made the code above not compile).

What am I missing? There must be something, or this would already by accepted practice for F-bounds.


Daniel Armak

Ahmad Salim Al-Sibahi

unread,
Apr 26, 2016, 11:09:44 AM4/26/16
to scala-debate, al...@cluonflux.com, jduc...@gmail.com, jxtp...@gmail.com
There was recently a paper in ACM Transactions on Programming Languages (TOPLAS) discussing a nice and correct way to add This types to a language if people are interested:


Ryu, S. (2016). ThisType for Object-Oriented Languages. ACM Transactions on Programming Languages and Systems, 38(3), 1–66. http://doi.org/10.1145/2888392

Kevin Wright

unread,
Apr 26, 2016, 11:22:31 AM4/26/16
to Ahmad Salim Al-Sibahi, scala-debate, Alex Cruise, Justin du coeur, jxtp...@gmail.com
I’ll wait for it to appear on arxiv.  Paywalls and broad discourse of new ideas don’t make for happy bedfellows.

Adriaan Moors

unread,
Apr 26, 2016, 1:10:48 PM4/26/16
to Daniel Armak, Alex Cruise, Justin du coeur, Jxtps, scala-debate
If we're talking about syntactic sugar for "the abstract type upper bounded by the current class as seen from the given prefix", that would indeed not be too hard but it would only be sound in covariant positions. Not convinced the utility outweighs the cost. 

Something more ambitious quickly becomes intractable (needing exact types, some way of disconnecting MyType from the hierarchy when you get into implementation subclasses etc)

Viktor Klang

unread,
Apr 26, 2016, 2:00:49 PM4/26/16
to Adriaan Moors, scala-debate, Jxtps, Alex Cruise, Daniel Armak, Justin du coeur

Ergonomically (read syntactically), I'd probably prefer:
this.type & this#type

--
Cheers,

Ahmad Salim Al-Sibahi

unread,
Apr 27, 2016, 7:21:03 AM4/27/16
to scala-debate, asal.t...@gmail.com, al...@cluonflux.com, jduc...@gmail.com, jxtp...@gmail.com
I didn't notice since I have access to ACM Digital Library.
Here is a link to earlier related work (from the author's website http://plrg.kaist.ac.kr/doku.php?id=research:publications):
Reply all
Reply to author
Forward
0 new messages