Proposal to disallow class shadowing


#1

Link: http://dotty.epfl.ch/docs/reference/dropped-features/class-shadowing.html

Scala 3 will no longer allow name clashes for nested classes. The following code will cease to compile and will have to be refactored:

class Base {
  class Ops { ... }
}

class Sub extends Base {
  class Ops { ... } // not allowed -- change name (or make private)
}

This compiles in the Scala 2.x series, but leads to confusion and compiler bugs. Usually, when members of classes (such as Ops) have the same name, we’d expect either overriding or overloading to occur. For class members, this is not the case, and the name is shadowed. Thus, forcing unique name choices will both be clearer to the user and much simpler to implement in the compiler.

One question for clarification: will a name clash be allowed for a private class? Technically, those are handled differently (they are not inherited, obviously, and thus do not play part in overriding).


#2

No name clashes would arise from private classes.


#3

In my use-case hiding all ancestors except for the parent class is much less confusing. I won’t be falling on the sword for this feature, but I’ll describe how I use it and why it’s more simple in my case.

I’m using name shadowing in my library to differentiate between a “user” API and a “developer” API, where a user just uses the API of the library and a developer can expand the library by knowing the internals. So I needed a way to clearly separate the two APIs, so the user isn’t exposed to a flood of irrelevant members, but the developer can still access them (both user and developer extend from the library’s traits and classes, so private[Lib] was not enough).

abstract class Foo {
  trait __Dev {
    def devFunc : Unit
  }
  lazy val __dev : __Dev = ???
  def userFunc : Unit ={}
}

class Bar extends Foo {
  trait __Dev extends super[Foo].__Dev {
    def devFunc : Unit = println("only for devs")
  }
  override lazy val __dev : __Dev = new __Dev {}
}

So without shadowing and many extending classes, it the lowest class will now be “flooded” with __DevXXX member traits, instead of a single __Dev. Again, this is not crucial, but it does have its uses.


#4

It seems it will be very bad improvement for us :(((

It is the common code for us

class Bfb_InventoryOrderAvi extends Bfb_InventoryOrderDvi {

  override def list(): List = {
    new List {
      override def meta = this
    }
  }
  trait Default extends super.Default{
     @Setter
    override def setidObj(event: SetterEvent): Unit = {
      super.setidObj(event)
      selection.refreshItem()
    }
  }
  trait List extends Default with super.List
  trait Card extends Default with super.Card
  trait Lookup extends Default with super.Lookup
}

Bfb_InventoryOrderDvi is automatically generated class.

Also we can make

class  Proj_InventoryOrderAvi extends  Bfb_InventoryOrderAvi {

trait List extends Default with super.List{
  ...
}
}

It will be headache for us to make magic names.
:frowning:


#5

For us it is quite crucial. Because such proposals force us to use magical unique names in everyday code :frowning:


#6

I agree that it can be inconvenient. But I believe the language simplification is worth it. Having same named members in subclass and superclass that don’t override each other, seems a small thing, but once you compromise on principles you set everything else adrift. Both Scala’s foundations and the Dotty compiler require that same named members override each other. So, essentially, we need to drop class shadowing or the whole Scala-3 project becomes unfeasible. Sorry.


#7

In our use case we do override each other

 trait Default extends super.Default

So with such proposal we will not be able to decompose logic in inner classes.
If we want to override such composition we will have to use magical names.
I can understand it for classes(jvm reflection), but traits are something that have many “magics” in jvm so I really do not understand such principles for traits.
It makes scala closer to kotlin. But we have chosen scala out of its extensibility(orientation to high level business logic).

I believe scala should be extensible
If scala were “simple” it would be java\kotlin


#8

Sorry, I have been a bit emotional in previous message.

Would You explain why trait shadowing makes Scala-3 project unfeasible?


#9

In our use case we do override each other

That’s a good illustration of the point: They don’t override each other, it just looks this way.

Would You explain why trait shadowing makes Scala-3 project unfeasible?

The rules are universal: A type reference p.T consists of a path p and a type name T. It has a denotation, which might refer to a unique symbol, or not. Because p can have an intersection type or union type, it could have several members named T. In that case the denotation of the reference is the intersection (respectively union) of all member denotations. In all this discussion, there’s no place that a type refers to a unique “symbol” a priori (1). But that’s precisely what would be needed to support shadowing.

(1) Also note that in the DOT calculus there’s no notion of “symbol” anywhere, names is all you have got.


#10

@AMatveev:
Wouldn’t moving inner traits to classes’ companion objects solve shadowing problems?

Instead of:

class Bfb_InventoryOrderAvi extends Bfb_InventoryOrderDvi {

  override def list(): List = {
    new List {
      override def meta = this
    }
  }
  trait Default extends super.Default{
     @Setter
    override def setidObj(event: SetterEvent): Unit = {
      super.setidObj(event)
      selection.refreshItem()
    }
  }
  trait List extends Default with super.List
  trait Card extends Default with super.Card
  trait Lookup extends Default with super.Lookup
}

Write:

package no_shadowing

class Bfb_InventoryOrderDvi {
  import Bfb_InventoryOrderDvi._
  def list(): List = new List {}
}

class Bfb_InventoryOrderAvi extends Bfb_InventoryOrderDvi {
  import Bfb_InventoryOrderAvi._
  override def list(): List = new List {}
}

object Bfb_InventoryOrderDvi {
  trait Default

  trait List   extends Default
  trait Card   extends Default
  trait Lookup extends Default
}

object Bfb_InventoryOrderAvi {
  import no_shadowing.{Bfb_InventoryOrderDvi => Super}
  trait Default extends Super.Default

  trait List   extends Default with Super.List
  trait Card   extends Default with Super.Card
  trait Lookup extends Default with Super.Lookup
}

#11

I do not think it is the best decision for us.

  • it will create unnecessary inner object
  • And more importantly, we occasionally use type parameters for example find[Bfb_InventoryOrderDvi.Bfb_InventoryOrderDvi.List]()

So of course it will work, but it will not look very pleasant.
I think we will use magic prefixes, and we will write yet another style guide :))

note:

trait List   extends Default

We need that ‘List’ would be inner class of dvi


#12

Thank you, I have understood motivation.

Would it be possible to implement ability which allows to disable inheritance but use such traits from others classes? for example:

class Bfb_InventoryOrderDvi extends View {

  isolated trait Default extends View.Default{
    
  }
  isolated  trait List extends Default with View.List
}

class Bfb_InventoryOrderAvi extends Bfb_InventoryOrderDvi {
  import no_shadowing.{Bfb_InventoryOrderDvi => Super}
  isolated trait Default extends Super.Default{
  }
  isolated  trait List extends Default with Super.List
}

Otherwise with such proposal we will have to add class name prefix to inner trait or use object. In any case it will not be very comfortable.


#13

If someone feels moved to write an introductory book to Dot and the Scala 3 compiler, I would certainly buy it.


#14

I am not sure how well it’d integrate into DOT and also into @AMatveev’s and @soronpo’s use cases, but what about switching to a model quite like Ceylon’s (formal/default) member classes?

Ceylon’s model is to effectively render nested classes as members just like methods etc. and to make them overridable as such! This should alleviate @odersky’s concern:

as well, right? Or did I miss anything big that’d make this not feasible?


#15

Or did I miss anything big that’d make this not feasible?

What you describe looks like virtual classes. It’s been an intensive research area for 15-20 years, stopped about 10 years ago. Why? Because the only formal treatment with a convincing soundness argument

https://dl.acm.org/citation.cfm?id=1111062

is so restricted as to be unusable. Concretely you can refer to inner classes only via this. So this.C is OK, but x.C is not.

So, yes, people have tried it and several dozen papers were published. It did not work out.


#16

Virtual classes, as they are also called, received a lot of attention in academia, including in the context of Scala, but I’m not aware of an approach that’s both practical and safe/sound. You can get quite close with abstract types and factory methods, but you’ll quickly run into problems that those types cannot safely be used because you don’t know which override (decided at runtime) you’re dealing with. Same problem as MyType.

As far as the examples I’ve seen here, how about one public base type and then a private implementation in each subclass. Private classes don’t cause name clashes.


#17

Has research examined “extension classes” (in analogy to extension methods)? My hunch is that it would be both sound and useful, but probably not worth the complexity budget. (After all, you can express the same thing with a typeclass-like mechanism to bind the outer and “inner” types; your only problem there is visibility unless you have a friend mechanism like C++.)


#18

if I understand your proposal correctly it will not work in multi layer decomposition.

For example, we have several layers in my example which is written above:

  • core layer (interfaces from application server)
  • application layer (default implementation)
  • code generation layer (dvi)
  • business logic layer (avi)
  • customisation layer (optional for specific configuration)

#19

Empty object is not a big deal, IMO. Inner classes and traits will be stored in extra class files anyway.

So what? You could still use them.

Inner trait of class has implicit reference to outer class which can be made explicit. Visibility of private methods is retained in my scheme. Look at this:

package no_shadowing

class Bfb_InventoryOrderDvi { self =>
  import Bfb_InventoryOrderDvi._
  def list(): List = new List {
    override def outer: Bfb_InventoryOrderDvi = self
  }

  private def privateOfBfb_InventoryOrderDvi(): Unit = ()
}

class Bfb_InventoryOrderAvi extends Bfb_InventoryOrderDvi { self =>
  import Bfb_InventoryOrderAvi._
  override def list(): List = new List {
    override def outer: Bfb_InventoryOrderAvi = self
  }

  private def privateOfBfb_InventoryOrderAvi(): Unit = ()
}

object Bfb_InventoryOrderDvi {
  trait Default {
    protected def outer: Bfb_InventoryOrderDvi

    def callerOfBfb_InventoryOrderDvi(): Unit =
      outer.privateOfBfb_InventoryOrderDvi()
  }

  trait List   extends Default
  trait Card   extends Default
  trait Lookup extends Default
}

object Bfb_InventoryOrderAvi {
  import no_shadowing.{Bfb_InventoryOrderDvi => Super}
  trait Default extends Super.Default {
    override protected def outer: Bfb_InventoryOrderAvi

    def callerOfBfb_InventoryOrderAvi(): Unit =
      outer.privateOfBfb_InventoryOrderAvi()
  }

  trait List   extends Default with Super.List
  trait Card   extends Default with Super.Card
  trait Lookup extends Default with Super.Lookup
}

Show me code which works with your scheme, but doesn’t work with my scheme.


#20

I do not undertand what do you try to prove me.
Lets disable shadowing at all(for inner variables etc)
And then I give you example wich will work.
Will it really help you?

I think it is good principle for system languages(‘c’). But it is awful for high level one it is a road to hell )))