Expunging `new` from scala 3

X.new will be confusing since it looks like new is a method in object X, but it’s not. It’s a method named this in class X. The naming here is inconsistent. I think to make this make sense we’d need to name the constructor method new and then have constructors all go in companion objects instead of the classes. Or use X.this, which I don’t think is better.

Additionally, since we want to be able to call the constructors from Java, the compiler would have to make the static new methods call the actual constructors, or vice versa.

1 Like

I don’t think there’s an issue with java interop. The dotty compiler can honour any fictions it wishes to, as long as it can biject between those fictions in the scala 3 source and the jvm back-end. Within the jvm, constructors are actually named <init>, not this or after the class name.

1 Like

Why not? At least if object X exists, new should absolutely be synthesized to be in it.

2 Likes

One idiom that I’ve observed in Scala code is to use new to construct objects for which equality is based on object identity, where each new object is fresh, distinct from all others, and to use a new-less syntax to construct objects without identity, intended to be referentially transparent, equal if and only if their components are equal. For example:

new Counter(0) != new Counter(0)

but

Point(1, 1) == Point(1, 1)

When teaching, the concept of equality and identity is often tricky for students, and I’ve found that having a stylistic cue has been helpful.

1 Like

Shouldn’t this be

class A(x: Int) { class B(x: Int) { ... } }

for a nested class? Otherwise A.new(1).B.new(2) doesn’t appear to make much sense.

I assume you mean new (new A(1)).B(2). But that’s only a syntactical limitation which makes sense because of the non-standard associativity. Semantically you can already do this by desugaring to

{ val a = new A(1); new a.B(2) }

This syntax creates an ambiguity between term names and type names. What happens if the same name does not refer to companions? Does it matter if the type name refers to a class with a companion object even though the term name references something else? Or the other way around: The term name refers to an object that has a companion class but the type name refers to some other type?

I think the most consistent interpretation would be to always treat C as a term name. But that raises further questions: What happens when there is no companion object? Do you assume that a class always has one (for consistency)? What if there is a companion object and you decide that C(...) refers to a constructor? Do you still initialize the companion object? (It would be more consistent but bad for performance).

1 Like

@Ichoran
Can you really imagine trying to explain to someone that the “method” (constructor) this in

class C(x: Int) {
  def this() {
     this(0)
  }
}
object C

is actually called with C.new? There is a complete disconnect here between the naming of new and this. The way it is now is familiar to Java people, and new being a keyword makes it clear that constructors are very special and not really methods. If we move to something like X.new then I need a decent story for those two points.

1 Like

I know we need a nod to backwards compatibility, but this is code I have never needed to write in scala. I have genuinely never written a def this constructor, at least not since my first week writing java-in-scala. I’d actually forgotten the capacity even existed.

1 Like

Mostly there with you – I think I’ve done def this fewer than half a dozen times in half a dozen years. I’m pretty sure it’s never necessary, and I haven’t found it terribly helpful in idiomatic Scala…

1 Like

I would instead imagine def new() = new(0). And I would expect the companion would pick it up. this(0) should be the apply method called on oneself, not a constructor call. How confusing!

That actually adds irregularity. Why object ObjectName extends Something is a definition, while object extends Something is an expression? Why can’t I write println(object O extends Something) but println(object extends Something) would be OK?

3 Likes

This would be much more idiomatically written as

class C(x: Int = 0) {
  ...
}
object C

As others have observed, secondary constructors are a seldom-used feature, and almost certainly should be discouraged.

I’m not sure what kind of argument that is. After optimizing, inlining, and hotspot operations, almost anything can be different. What matters is that it has the same outcome and you have an idea of the worst possible performance.

I have no idea what you mean by this.

Really, I do not see any motivation for this proposal.

2 Likes

@szeiger

This syntax creates an ambiguity between term names and type names. What happens if the same name does not refer to companions? Does it matter if the type name refers to a class with a companion object even though the term name references something else? Or the other way around: The term name refers to an object that has a companion class but the type name refers to some other type?

Yes, that’s a tricky point. I believe it will mean that all non-trait classes are given companion objects. We don’t need to generate code for them, but the typer needs to pretend they exist. This is a backwards incompatible change. I am not sure how serious the breakage would be,

1 Like

I think I’ve done def this fewer than half a dozen times in half a dozen years. I’m pretty sure it’s never necessary , and I haven’t found it terribly helpful in idiomatic Scala…

We have ~20 def this usages in our (big, old, rambling) Scala codebase. Some are not entirely trivial to replace. There are two cases where we use them:

  1. Creating secondary constructors in base classes so extending child classes can call them in their primary constructor.
    Sometimes this is just for convenience, e.g. an abstract class with several constructors.
    Other times it’s for Java interop. A typical case is with java.lang.Exception, which has three constructors with different numbers of arguments. Subclasses of Exception often also define several constructors, to make generic/reflective Java code work well.
  2. Secondary constructors in non-case classes so callers don’t have to figure out when to use new and when apply, and can easily see all the overloads. If we got the same apply-like syntax for all constructors, this problem would go away.

Secondary constructors are absolutely critical for Scala.js to be able to accurately model JavaScript APIs. They are also obviously critical for Java interop.

And even without taking those aspects into account, there are 30 secondary constructors in the Scala.js codebase, most in public APIs that are subject to binary compatibility guarantees (so they cannot be replaced by default arguments, for example).

1 Like

Wouldn’t the biggest boiler plater killer in this area be to allow Java style Static methods within the body of the class? The fact that these methods may be implemented through a separate singleton object, doesn’t stop me from finding the creation of two syntactic top level blocks, nearly always unhelpful for code organisation. On the minority of occasions where I do have both a large class trait and a large companion object, I often would like to group methods from class and singleton for better comprehensibility.

I have thought so, too.
But actually it is not enough for me.
I also need the “static inheritance”.
For example:

  class Wms_BuildingDpi extend static ApiFactory
  class Wms_BuildingApi extend Wms_BuildingDpi 

Actually, now we always write

class Wms_BuildingApi extends Wms_BuildingDpi
object Wms_BuildingApi extends ApiFactory

or

class AppCodeNotImplemented(params:AnyRef) extends ApiException(params)
object AppCodeNotImplemented extends ExceptionFactory(new AppCodeNotImplemented(_))

I don’t understand the binary-compatibility argument. Is Scala 3 making any promises to allow binary compatible compilation of 2019 code based on Scala 2.12? Surely that can’t be the case (not even Scala 2.13 is binary compatible with 2.12).

I’m not talking about binary compatibility between versions of Scala. I’m talking about binary compatibility between versions of my library (within one version of Scala).