Thanks for the examples! So let’s think about the simplest rule that would pretty much work.
Let’s say that is it. There’s nothing else going on: it is entirely a syntactic, not a semantic desugaring, save that we need the semantic awareness of whether a type is already expected at a position.
We probably need [...]
to function as a type inference barrier. Although one can imagine a solver that would figure out from the types inside [...]
what it could possibly match, and from that figure out what calling types could be expected, and so on, it would be extremely opaque to programmers even if the compiler could often figure out the puzzle.
Furthermore, we’re expecting [...]
to parallel method arguments (granted, only on the apply method, but that can be as general as any method). That means we need to figure out what to do about named arguments, multiple parameter blocks, and using blocks. For example, what if we have
def apply(foo: Foo)()(bar: Bar)(using baz: Baz) = ???
But hang on! Relative scoping was already suggesting that we use .
(or ..
) to avoid having to specify the class name over and over again. Bare .
in the right scope would then just be…apply
!
So if we write
val xs: Array[Person] = .(
.("John", .(1978, 5, 11)),
.("Jane", .(1987, 11, 5))
)
it’s arguably the exact same feature, and since we are literally using the same syntax for the constructor/apply call (with .
in for the class name), there aren’t any weird gotchas to think through. Everything already works; the only thing we need to specify is where the relative scope is “we expect this type”.
This was suggested for things like .Id(3)
, where a single dot looks reasonable, but to me anyway it looks extra-weird without any identifier. Even though it’s longer, the double dot feels better to me:
val xs: Array[Person] = ..(
..("John", ..(1978, 5, 11)),
..("Jane", ..(1987, 11, 5))
)
So I think the two features end up completely unified at this point.
class Line(val width: Double) {}
class Color(r: Int, g: Int, b: Int):
val Red = Color(255, 0, 0)
class Pencil(line: Line, color: Color) {}
val drawing = Pencil(..(0.4), ..Red)
would just work, all using the same mechanism.
Furthermore, it is hard to see why [0.4]
should work and .Red
or somesuch should not work. The “type is known and saying the name over again is redundant” thing is similarly bad.
Catching two birds with one net seems appealing to me.