New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enhancement] introduction of some `public` form of Self-types for better `HasThisType` implementation (and to resolve `this.type`-upper-bounded-types vs Self-types contradiction) #5880

Closed
ibaklan opened this Issue Feb 8, 2019 · 1 comment

Comments

Projects
None yet
2 participants
@ibaklan
Copy link

ibaklan commented Feb 8, 2019

Motivation

During my experiments with possible different implementations of HasThisType, I've basically end up with several approaches.

approach#1

    trait HasThisType {
      type This >: this.type
    }

approach#1

    trait HasThisType[PThis] {
      this: PThis =>
      type This = PThis
    }

What I was also trying to achieve - is possibility "literally" see that.type <: that.This (not only inside HasThisType sub-class/sub-trait in form of this.type <: this.This, but anywhere in code where some val that: HasThisType is available).
Approach#1 basically provides that that.type <: that.This overall good visibility in most straight and obvious way.
However it suffers on some regression issue#5876, and it basically inappropriate when one may wont to define parametrized ThisLike1[_] abstract/self-type instead of simple This-type.
On the other hand one could try to combine that approaches together (and use advantages of Self-type together with public visibility of >: this.type upper-bounded constraint). Then something like following could be written

      trait HasThisTypeBase {
        type This >: this.type
      }

      trait HasThisType[PThis] extends HasThisTypeBase {
        this: PThis =>
        type This = PThis
      }

Unfortunately that attempt currently does not lead to expected effect (see issues: issue#5879 and issue#5878)
But if aforementioned issues will be "positively resolved" (so that not by claiming that as "expected behavior", or by applying some patch which will prohibit such code constructs at all), one could think about some more generalized solution (e.g. think about some possibility to define some "publicly visible" Self-types)

What is actually proposed

Possibility to control "visibility" of Self-types constraint, allowing them to have protected visibility (by default, and what they basically have now) and public visibility (newly introduced, visibility which should allow to see this.type <: this.Self constraint not only inside of underlined class/trait body, but also from outside as well)
Demonstrating snippet could look like following:

    trait AdvancedFoo[P1,P2,P3] {
      // hypothetical publicly visible Self-type constraint
      public this: P1 with P2 =>
      // hypothetically by default Self-type constraints need to have `protected` visibility
      protected this: P3 =>

      type T1 = P1
      type T2 = P2
      type T3 = P3
    }

Here, assuming that we have val that: AdvancedFoo[...], publicly visible Self-type need to be seen literally as that.type <: that.P1 with that.P2
(while P3-self-type constraint generally could be seen only as this.type <: this.P3)
Also it assumes that AdvancedFoo[P1,P2,P3] need to be transparently convertible to
AdvancedFoo[P1,P2,P3] with P1 with P2 (and wise verse).

What is possible even now

For now one could rewrite aforementioned hypothetical code into real(compilable) code in following way:
(complete code snippet here, same code also used in issue#5877)

    // `AdvancedFooSelf` represents "public" part of self type
    type AdvancedFooSelf[P1,P2,P3] = AdvancedFoo[P1,P2,P3] with P1 with P2

    trait AdvancedFoo[P1,P2,P3] {
      this: P1 with P2 with P3 =>

      type T1 = P1
      type T2 = P2
      type T3 = P3

      // code to expose it self type
      type SelfB >: this.type <: AdvancedFoo[P1,P2,P3] with P1 with P2 // SelfB <: AdvancedFooSelf[P1,P2,P3]
      inline
      def self(): this.type with P1 with P2 = this // self(): AdvancedFooSelf[P1,P2,P3]
    }

    // explicit and implicit converters to "expose it self"
    inline
    def exposeItSelf[P1,P2,P3](that: AdvancedFoo[P1,P2,P3]): that.type with P1 with P2 = that.self()
    inline
    def exposeItSelf1[P1,P2,P3](that: AdvancedFoo[P1,P2,P3]): AdvancedFooSelf[P1,P2,P3] = that.self()
    inline
    def exposeItSelf3[P1,P2,P3](that: AdvancedFoo[P1,P2,P3]): AdvancedFooSelf[P1,P2,P3] = that : that.SelfB

    object exposeItSelfImpl {
      import scala.language.implicitConversions
      implicit
      inline
      def expose[P1,P2,P3](that: AdvancedFoo[P1,P2,P3]): that.type with P1 with P2 = that.self()
    }

Here AdvancedFoo#self is used to somehow expose that
AdvancedFoo[P1,P2,P3] ~=:=~ AdvancedFoo[P1,P2,P3] with P1 with P2
And exposeItSelf, exposeItSelf1, exposeItSelf3 shows how that conversions could be implemented in form of generic methods. exposeItSelfImpl.expose allows to install that conversion as implicit, so in that case signatures
[P1,P2,P3](that: AdvancedFoo[P1,P2,P3]) could be adapted to signatures
[P1,P2,P3](that: AdvancedFoo[P1,P2,P3] with P1 with P2)
automatically (by compiler itself)

Demonstration of that explicit/implicit conversion (showing different ways how to "adapt" generic signature) may look like following
(complete code snippet here, same code also used in issue#5877)

      // target signature is `[P1,P2,P3](that: AdvancedFoo[P1,P2,P3] with P1 with P2)`

      // def doSmthWithSelfImpl[P1,P2,P3](that: AdvancedFoo[P1,P2,P3] with P1 with P2): Unit = ???
      def doSmthWithSelfImpl[P1,P2,P3](that: AdvancedFooSelf[P1,P2,P3]): Unit = {
        println(s"doSmthWithSelfImpl: that: $that")
      }

      // different ways of explicit and/or implicit signature adaptations
      // from `[P1,P2,P3](that: AdvancedFoo[P1,P2,P3])` to `[P1,P2,P3](that: AdvancedFoo[P1,P2,P3] with P1 with P2)`

      def doSmthWithSelf[P1,P2,P3](that: AdvancedFoo[P1,P2,P3]): Unit = {
        doSmthWithSelfImpl(exposeItSelf(that))
      }

      def doSmthWithSelfUsingImplicits[P1,P2,P3](that: AdvancedFoo[P1,P2,P3]): Unit = {
        import scala.language.implicitConversions
        import exposeItSelfImpl._
        doSmthWithSelfImpl(that)
      }

      def doSmthWithSelfWithWildcards(that: AdvancedFoo[_,_,_]): Unit = {
        // explicit conversion - signature1
        doSmthWithSelfImpl(exposeItSelf(that))
        // explicit conversion - signature2
        doSmthWithSelfImpl(exposeItSelf2(that))
        // explicit conversion - signature1
        doSmthWithSelfImpl(exposeItSelf3(that))

        // implicit conversion
        {
          import scala.language.implicitConversions
          import exposeItSelfImpl._
          doSmthWithSelfImpl(that)
        }
      }
@OlivierBlanvillain

This comment has been minimized.

Copy link
Contributor

OlivierBlanvillain commented Feb 8, 2019

Thanks for opening these very detailed issues! I'm going to close this one right away given that this issue tracker is not the place for language change proposal. Unless you personally intent on prototyping an implementation on top of Dotty, it's very likely that nobody will work on this, making the issue essentially non actionable. I encourage you to move it to https://contributors.scala-lang.org/ which would be more appropriate for discussion.

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