I wouldn’t even bother with the dot. What is the point of it? In particular, if you have
trait T {}
object T {
val a = new T {}
object B extends T {}
def c(): T = new T {}
}
def f(t: T) = ???
then f( )
automatically imports the companion object scope, so f(a)
and f(B)
and f(c())
just work. There are approximately zero downsides for this. If instead of being explicit, a
, B
, and c()
were implicits (givens in dotty), they’d be found. So why not find them if something explicit is requested?
To keep it from getting unwieldy, we could restrict it to only sealed traits or classes (or enums, in dotty).
Then you just have
val redCircle = Shape(Circle, Red)
with no extra fuss. The dot seems unnecessary extra clutter. The chance for confusion is minimal. Linting tools could catch the case where the auto-import introduced an ambiguity. For example, if you have
def environment(light: Light, water: Water) {}
sealed trait Light {}
object Light {
object Off extends Light {}
object On extends Light {}
}
sealed trait Water {}
object Water {
object Off extends Water {}
object On extends Water {}
}
then environment(Off, On)
throws away all the safety beyond just having a boolean. So linters could catch that and say something like, "ambiguity in inferred import: Light.Off
imported, but not distinguishable from Water.Off
".
I don’t think the generality problem that @kai mentions is actually much of a problem. Getting nice syntactic sugar for general classes of problems can require undue effort from the compiler. But this precise problem is already solved in a more difficult form for implicits, so in principle it should be achievable. (Whether the compiler architecture makes it easy enough to implement I do not know.)
Specifically, the rule I’d favor is
- For any expression where the type is known (e.g. because of type ascription or method parameter), and
- That type is a sealed trait or class (or an enum in dotty), and
- That trait or class has a companion object, then
- Every symbol in that companion with a type that is a subtype of the sealed trait or class is visible in this expression.
So you could do stuff like
val c: Shape.Color = Red
{ println(Square); Rectangle }: Shape.Geometry
as well. (The latter perhaps being questionable form, but I would admit it as a necessary consequence of simple lookup rules.)