Read this discussion once again, really interesting. Briefly saying, I am rather found my self in that @soronpo, @AMatveev, @Adowrath “camp”, and it seams that I have to disagree with @odersky’s opinion (related to hypothetical “virtual classes”)
My first impression about that idea was that protected
should/could be enough in this case (like: no need isolated
, just treat protected
in the way similar to private
, but impose strict overriding constrain, which anyway all ‘shadowers’(authors who use that) follow by their own will)
At least it covers @odersky’s concern about
in case if p
is not this
-expression and not super[...]
-expression protected
members will not violate that universal ruler of p.T
(since they will be simply invisible though that notation, same as private
ones)
Hoverer other @odersky’s concern about
will remains partially valid - one will not be able to refer parents (or even worse - “grand-parents”) hidden/shadowed nested classes (while it looks entirely “OK” for extends
-case , it will become NOT OK if someone use that hidden class in some non-private
method definition, in parent class)
Hoverer eventually (anyway) some form of “virtual classes” looks for me more attractive here.
I am not sure, whether I understood that point correctly, as for now it looks that I should disagree with that position.
As for my understanding “virtual classes” are currently (already) available in Scala, that could be easily expressed through abstract-types + self+types + abstract factory methods (the only basic thing that is missing - some syntax sugar for that construct - and that’s it (IMHO) …)
So from my understanding, here is valid definition of virtual nested/inner trait: complete snippet here
trait FooLike {
// main part - name definition (via abstract type)
type VirtualInner <: VirtualInnerPart
// `new VirtualInner(...)` replacement - any kind of constructors cold not be called directly,
// so they basically should be explicitly replaced with some number of abstract factory methods on outer level
def VirtualInner(): VirtualInner
// virtual trait body - self-type need to be used to achieve strict overriding constraint
trait VirtualInnerPart {
this: VirtualInner =>
}
}
- if this could not be called “virtual class/trait” definition then I will appreciate any explanations “why” ?
If I understood correctly @adriaanm’s words
he refers to something similar to that code snippet.
Remarkable here, that VirtualInnerPart
is in fact basically “useless” anywhere in code except extends
statement.
In particular, there are no reasons to use VirtualInnerPart
in methods/members definitions, since VirtualInner
will be “better” anyway
VirtualInner
-type provides everything that VirtualInnerPart
can, because of VirtualInner <: VirtualInnerPart
, and eventually any
VirtualInnerPart
instances should be anyway “up-castable” to VirtualInner
because of Self-type constraint this: VirtualInner =>
So what I am trying to say is that under perfect conditions I would prefer that VirtualInnerPart
become anonymous at all, and the only way to deal with it - should be using VirtualInner
type
(this also assumes that at some point type VirtualInner
should become concrete - in that place VirtualInner
name should “materialize” itself and become name/alias of some “non-overridable” class/trait - so that name VirtualInner
become “frozen” from that time)
Maybe it could be not fully clear here, how it relates to highlighted problem, so I will try to explain that by rewriting of few examples.
(But briefly, main idea here that inner class “name abstractness” and “body abstractness” should be separated and decoupled)
So here is “raw” rewrite of @soronpo’s snippet (it does not contain any syntax novelties, only prepares for them)
abstract class Foo {
type __Dev <: __Dev_SyntheticPart0001
trait __Dev_SyntheticPart0001 {
// use self-type constraint to enforce proper overriding instead of "shadowing"
this: __Dev =>
def devFunc : Unit
}
lazy val __dev : __Dev = ???
def userFunc : Unit ={}
}
abstract class Bar extends Foo {
// noticeable drawback here - that it remain overridable until name is abstract, but once name become concrete, "name" override-ability is gone
type __Dev <: __Dev_SyntheticPart0002
trait __Dev_SyntheticPart0002 extends __Dev_SyntheticPart0001 {
// use self-type constraint to enforce proper overriding instead of "shadowing"
this: __Dev =>
def devFunc : Unit = println("only for devs")
}
override lazy val __dev : __Dev = ???
}
class BarImpl extends Bar {
type __Dev = __Dev_SyntheticPart0003
trait __Dev_SyntheticPart0003 extends __Dev_SyntheticPart0002
override lazy val __dev : __Dev = new __Dev {}
}
Here it is clearly seen, that SyntheticPartXXXX
-names are in fact useless (and could be hidden under some “compiler sugar”)
And then (using some hypothetical @virtual_abstract_type
modifier and @virtual_concrete_type
modifier) snippet about should be rewritten to following equivalent snippet:
abstract class Foo {
@virtual_abstract_type trait __Dev {
// proposed construction should implicitly use self-type constraint to enforce proper overriding instead of "shadowing"
// this: __Dev =>
def devFunc : Unit
}
lazy val __dev : __Dev = ???
def userFunc : Unit ={}
}
class Bar extends Foo {
// noticeable drawback here - that it remain overridable until name is abstract, once name become concrete, "name" override-ability is gone
@virtual_abstract_type override trait __Dev extends super[Foo].__Dev {
// proposed construction should implicitly use self-type constraint to enforce proper overriding instead of "shadowing"
// this: __Dev =>
def devFunc : Unit = println("only for devs")
}
override lazy val __dev : __Dev = ???
}
class BarImpl extends Bar {
@virtual_concrete_type override trait __Dev extends super[Bar].__Dev
override lazy val __dev : __Dev = new __Dev {}
}
(Here also need to be noted that under extends super[Foo].__Dev
I see only some formal notation, in any other places (except extends
) super[Foo].__Dev
should NOT be used and/or should generally reference to this.__Dev
as it does now in Dotty)
Basically that hypothetical construction should solve problem of “diverse” names like __Dev_SyntheticPartXXX
availability in scope - they should be simply “invisible” (names itself should be internally assigned by compiler in any reasonable way), however it will introduce some other problem - transition form “abstract name” to “concrete/frozen name” now need to be explicit (and probably require introduction of yet an other class in outer classes hierarchy - here it is BarImpl
)
Going even further - one may see that @soronpo’s snippet is in fact some way of declaring some “lazy abstract virtual object”.
In particular, for that sort of use-cases I would imagine even more specific form of “syntax sugar”
/*
it is not valid/compile-abe Scala code - since it uses hypothetical abstract/lazy objects notation
*/
abstract class Foo {
abstract lazy object __dev {
// such construction should implicitly use self-type constraint bound to `__dev.type`, at least it looks that Dotty behalves this way
// this: __dev.type =>
def devFunc : Unit
}
def userFunc : Unit ={}
}
class Bar extends Foo {
// note drawback here - that it remain overridable until name is abstract, once name become concrete, "name" override-ability is gone
override abstract lazy object __dev extends super[Foo].__dev {
// such construction should implicitly use self-type constraint bound to `__dev.type`, at least it looks that Dotty behalves this way
// this: __dev.type =>
def devFunc : Unit = println("only for devs")
}
}
class BarImpl extends Bar {
// non `abstract` objects names should automatically become `final`
override final lazy object __dev extends super[Bar].__dev
}
- this suggestion was in fact inspired by internally generated Dotty code (which I’ve spotted while observing dotty crashes - in fact in Dotty objects are represented as val
-s, and their bodies internally defined as a classes with self-type constraints of form this: __dev.type =>
)
Actually this approach could be mechanically extent to @AMatveev’s case, however in that situation it looks much more tricky.
And by saying “tricky” I mean “mixing of virtual classes” (assuming that “virtual classes construct” understanding matches to that approach mentioned above).
Generally it looks simple - all Default
, List
, Card
, Lookup
need to be internally represented as abstract types, and their body should be treated as some “anonymous”(with generated names) traits with appropriate self-tye constraints. At some point all that names should be “frozen” (abstract types should become simple type aliases) from that point (in outer types hierarchy) they will be capable to participate in new
clause.
Interesting here that even if we only “refine” (“override”) Default
“virtual trait” generated self-types constraints on List
,Card
, Lookup
bodies will force us to keep List
,Card
,Lookup
bodies consistent with up-to-date Default
in that place where List
,Card
, Lookup
names become frozen (and immediate implication here that names List
,Card
,Lookup
could NOT become frozen “earlier” then Default
name become “frozen” - because List
,Card
,Lookup
has dependency on Default
).
But anyway, I could not see any “soundness” issue (even in this case)
So here is that suggested “raw rewrite” (this is how generated code might look like after hypothetical “de-sugaring” of “abstract virtual traits”)
class Bfb_InventoryOrderAvi extends Bfb_InventoryOrderDvi {
/*
// could not be migrated "directly" since `new List` will not be available until `List` name is resolved to abstract type (and not simple type alias)
// this sort of methods should be defined as abstract factories, and could be implemented only at that point in class hierarchy where name `List` become "frozen"
override def list(): List = {
new List {
override def meta = this
}
}
*/
type Default <: Default_SyntheticPart0002
trait Default_SyntheticPart0002 extends super.Default_SyntheticPart0001 {
this: Default =>
@Setter
override def setidObj(event: SetterEvent): Unit = {
super.setidObj(event)
selection.refreshItem()
}
}
type List <: Default with List_SyntheticPart0002
trait List_SyntheticPart0002 extends Default_SyntheticPart0002 with super.List_SyntheticPart0001 {
this: List =>
}
type Card <: Default with List_SyntheticPart0002
trait Card_SyntheticPart0002 extends Default_SyntheticPart0002 with super.Card_SyntheticPart0001 {
this: Card =>
}
type Lookup <: Default with List_SyntheticPart0002
trait Lookup_SyntheticPart0002 extends Default_SyntheticPart0002 with super.Lookup_SyntheticPart0001 {
this: Lookup =>
}
}
Here also (probably) could be seen what was hinted by @odersky
But it looks that that statement has limited “scope” - that.Default
is in fact valid anywhere in outside code, where we have that: Bfb_InventoryOrderAvi
reference, but yes, if we are talking about usages of that.Default
in extends
statement (somewhere outside Bfb_InventoryOrderAvi
body), then definitely such code will not be “OK” (but I think nobody of those “nested abstract-virtual classes” real users ever want or need such construct)
So in particular if one have code like
trait Outer {
trait Inner
}
trait Other {
val s: Outer
trait Strange extends s.Inner
}
Of-course trait Strange extends s.Inner
will not work if Outer#Inner
will be defined as abstract virtual trait (since s.Inner
will refer to abstract type in that case and that entire construct trait Strange extends s.Inner
will be defiantly invalid)
But in any reasonable cases of nested classes overriding (mentioned in this discussion) that kind of constructs was never referred to.