The way I see it, there are currently two issues with extension methods:
- They pollute non-extension namespace
- Some unintuitive gymnastics are needed to make them work when right-associative
trait Showable[T]: extension (x: T) def show: String
We can now use it like so, even though this is not communicated at all by the declaration:
On its own, this is not so bad, at least it allows us to do the following, right ?
given Showable[Int] with override def show(x: Int) = x.toString
Well, no it doesn’t (scastie), forcing us to write the following:
given Showable[Int] with extension (x: Int) override def show = x.toString
(To be clear: I think allowing it as-is would be going in the opposite direction I’m advocating for, but showcases that there is already precedent for making a hard distinction between extension and non-extension methods.)
Given all of this, it would be clearer to write the above as:
trait Showable[T]: def show(x: T): String extension (x: T) final def show: String = show(x) given Showable[Int] with override def show(x: Int) = x.toString
show was a complicated method, it might be nice not to have it in the middle of an extension block, which tends to look a bit cluttered and confusing.
Sadly, this does not work (scastie)
Ambiguous overload. The overloaded alternatives of method show in trait Showable with types (x: T): String (x: T): String both match arguments ((x : T))
because the extension method already declares that non-extension version, as we have seen before.
Because the parser swaps the parameters of right associative methods at use-cite, we need to preemptively swap them ahead of time.
The parser does not know yet if these are extension methods or not, as they look syntactically identical.
Extension methods like
extension (x: T) def show: String = body def <:(l: List[T]): Boolean = ???
Are desugarred into
<extension> def show$extension(x: T): String = body <extension> def <:$extension(x: T)(l: List[T]): Boolean = ???
As before, methods ending in
: are parsed right-associatively, and get their parameter swapped.
But methods ending in
:$extension are only parsed right-associatively, no parameter swaps.
This allows us to do our initial:
trait Showable[T]: def show(x: T): String extension (x: T) final def show: String = show(x)
As it gets desugarred to:
trait Showable[T]: def show(x: T): String <extension> final def show$extension(x: T): String = show(x)
There is no name clash anymore between the two methods, in particular, we can now be certain the second method is not recursive.
We can identify methods previously generated by noticing those all have an
<extension> tag, but not a name containing
$extension, we can therefore handle these differently when needed.
For example an
<extension> foo can be called as
foo(x), but an
<extension> foo$extension can only be called as
(There might very well be other concerns, help is welcome)