Skip to content

Decide on status of new {} #10637

Closed
Closed
@odersky

Description

@odersky

The Current State

The current Dotty implementation allows to leave out the parent of an anonymous class if it is known from the expected type. So the following compiles

trait C { type T; val m: T }
def f(x: Int): C = new { type T = Int; val m = x }

This avoids duplication since we would have to write otherwise

def f(x: Int): C = new C { type T = Int; val m = x }

This matters if the name C is long or there are type parameters.

Likewise, the following also compiles:

def g(x: C) = ???
def test = g(new { type T = String; val m = "" })

That's a bit more dubious since the name C does not appear anywhere in the call, so this looks like a pure structural new that uses reflection for dispatch.

None of this is currently specified except for a single line in syntax.md:

SimpleExpr ::= ... |  ‘new’ TemplateBody

A Possible Alternative: Structural Instances

  • Use C with { ... } as an anonymous class
  • Allow C with { ... } as the result type of a def without having to write a RHS, mimicking what we do for givens.

This means the first example above would be written like this:

trait C { type T; val m: T }
def f(x: Int): C = C with { type T = Int; val m = x }

which can be abbreviated to

def f(x: Int): C with { type T = Int; val m = x }

In fact the abbreviated form is more useful than the original forms since the result of f is known to bind T to Int.

There was an explanation about this in #10538. Quoting here:

This has another interesting consequence. Taken by itself, can we give a meaning to T with { defs }? In fact, this does make sense as an alternative syntax for an anonymous class. If we go down that path, we can truly get rid of all vestiges of new in the syntax (over time, not for 3.0).

Taking this further, we can now see the following (approximate) equivalence:

given x: C with { ... }     ~~~      given x: C = C with { ... }   ~~~    given x: C = new C {...}

So, : C with {...} is approximately : C = C with {...}, and it avoids the repetition. In fact : C with {} is more useful than : C = C with {...} since it keeps any refinements in {...} in the type. So it's more like a RHS = new {...} and an inferred result type. In fact it's even better than that, since an anonymous class new { ... } is subject to avoidance, but : C with { .. } creates the class alongside the given instead of in its right hand side. This means that members freshly introduced in {...} are visible in the result of the given. To see the difference, consider this code:

class C
given c: C with
  def foo = 1

given d: C = new C { def foo = 1 }

def test =
  c.foo  // OK
  d.foo  // error: `foo` is not a member of `d`.

Now, one intriguing step further is whether we want to allow the same convention for normal defs. I.e

def foo(x: Int): T with {...}

as a slightly more powerful alternative to

def foo(x: Int) = T with {...}

or, using new

def foo(x: Int) = new T {...}

with the same expansion as for the given. This would give us parameterized objects, or functors in the SML sense, without having to define a class. It also gives a nice way to avoid duplication between return type and RHS while stile having an explicitly defined return type.

What to do?

We should not adopt both new {...} and structural instances. I believe structural instances are ultimately a lot more powerful and useful than new {...}, in particular since they give us the power of object (which can define new members not covered in the extends clause) with parameters.

So our choices are:

  1. Spec new {} for 3.0 and close the door for structural instances.
  2. Adopt structural instances for 3.0, including spec and implementation. We might be too late for this.
  3. Drop new {}, keeping the door open for structural instances later.
  4. Same as (3), except we put new {} under an experimental flag for now.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions