Relative scoping for hierarchical ADT arguments

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

  1. For any expression where the type is known (e.g. because of type ascription or method parameter), and
  2. That type is a sealed trait or class (or an enum in dotty), and
  3. That trait or class has a companion object, then
  4. 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.)

6 Likes