Proposal: Creator applications (going new-less)

Hello Scala community!

This thread is the SIP Committee’s request for comments on a proposal to allow instance creation without using new.

Summary of the proposal

In short, the proposal is to allow e.g.:

class C(x: String)
C("foo")   // same as `new C("foo")`

Creator applications generalize a functionality provided so far only for case classes, but the mechanism how this is achieved is different. Instead of generating an apply method, the compiler adds a new possible interpretation to a function call f(args).

Leaving out new hides an implementation detail, makes code more pleasant to read, and improves consistency between case classes and ordinary classes.

For more details of the proposal, see the documentation page on the Dotty site:

Related links

In informal discussions so far, the committee has been more supportive than not, but there has also been some skepticism (from e.g. Seb and Seth). See the following minutes for details:

Other relevant resources and past discussions in this area include:

Perhaps I’ve missed some; if you know of any, please reply with links.

For discussion

  • Is the proposal clear and detailed enough?
  • Should this proposal be accepted by the committee for Scala 3?
  • Should the proposal be modified before acceptance?

Time frame

This topic will remain open for at least one month, to allow sufficient time to gather feedback.

Ground rules

As with all SIP threads, please try to keep the thread on-topic and of reasonable length. If a sub-discussion is becoming extensive, it may be best to move it to a separate topic, and then summarize that discussion here with a link, for those interested.

9 Likes

To be clear, this propsal does not include removing new, but only allow constructing an object without it, right?

5 Likes

I’m opposed.

On the one hand,

is a huge cost IMO. We’re making code harder to understand. If I see f(args) where do I look to see where that calls to?

Besides which, instantiating a class and calling code are really very different things. Why would we want to conflate them?

Furthermore it complicates the language even more. (I thought dotty was about simplifying the language…)

So on the one hand the cost is high IMO.

And on the other hand, what are the benefits? What is the motivation?

I don’t agree with any of those.

  1. new is not an implementation detail any more than anything else. I want an instance of a class. That’s what the code is doing. Why is it better to encourage hiding that that’s what I’m doing? Maybe we should have some syntactic sugar for variable reassignment that makes it look like something else entirely?

  2. I don’t agree that new is unpleasant to read. And if it is, and instantiation is indeed a low-level “implementation detail,” then that’s a good thing, like .asInstanceOf. In any case it’s not noise it’s signal, and hiding signal just because it looks prettier without it is a dangerous road to go down IMO. Furthermore, the goal should not be pleasantness to read but easiness to read and understand. Removing new makes it harder to read and understand the code.

  3. Why do we want non-case classes to be more consistent with case classes? The whole point of case classes is that they generate a bunch of boilerplate for you. There has been discussion of making this more fine-grained (case classes a la carte). But a plain old scala class is not a case class. It doesn’t have a special equals, hashCode, toString, unapply, copy, or anything else. Why should it behave as if it has an apply method? Or rather, why should we change the language spec so that case classes don’t need an apply method just to pointlessly blur the distinction? In fact, I would argue the distinction is a good thing. I want non-case-class instantiations to stand out. They often require thinking twice.

In short, IMO the benefits are minute (and subjective) if they even exist, and the costs are high, making Scala harder to learn and code harder to maintain.

7 Likes

I am opposed.

As for now the notation C("foo") is a shortcut for C.apply("foo"). Admittedly, in most cases, apply is used as a static factory for its companion class, but nothing in the language limits apply to this use-case. For example, in most Akka examples, apply is used to initialize a behavior, and not to return an instance of its companion-class.

Even though the C("foo") notation is mostly used to create new instances of the companion-class of some sort, there is no inherent connection between apply and the constructor of a class.

Using the same notation for two completely unrelated concepts is not user-friendly and, for sure, not beginner-friendly.

Plus, in my opinion, static factories should always be given preference over constructors when it comes to creating a new object. Therefore having a concise notation for calling the static factory method is a nice side-effect that would encourage developers to do so.

Admittedly, I am relatively new to Scala, so that I might be overseeing things. This are the views from a beginner’s point of view.

1 Like

I have never met such troubles in my practice. I usually have to write apply to instantiate a class because constructor is quite restrictive.
It is quite common situation when I create a constructor at first, then I use it, after some time I understand that the constructor does not fit at all, and I have to create factory and make refactoring which is quite annoying.
So when I use a constructor I quite often do not know whether it will be better to create factory to prevent refactoring in future or it will be boilerplate code. It is not good situation in general. I would prefer just do not think about it.

I like it. It is extremely inconsistent to have to use new all the time.

4 Likes

I’m in favour.

In my view the counter-arguments brought forward downplay the fact that the picture is already blurred due to the existence of case classes.

As a newbie I don’t care and I am not aware that a case class by default has an auto-generated companion. I have to learn the (seemingly arbitrary) distinction that one gets instantiated with new and the other doesn’t. As someone who has taught Scala to newcomers frequently I see this as a simplification.

3 Likes

I’m in favor.

Kotlin and Python have this syntax for instantiating classes. Both languages have their own version of def apply for overloading function-call syntax (invoke and __call__ respectively). There are zero complaints about confusion between function calls, method calls, and class instantiation. While it is reasonable to have concerns about ambiguity, empirically it seems like a non-issue in practice.

11 Likes

Also in favor. One of the more common bad smells in Scala is folks abusing case classes solely in order to avoid needing to write new. The demand is there, and it’s not going away – IMO, we should just support it, and be done with the matter.

12 Likes

I’m in favor. Kotlin has this and it’s always felt great. I often forget to write new when going from Kotlin to Scala, the same way I forget to add semicolons when going back to Java.

In my experience it hasn’t been confusing for myself or other developers.

6 Likes

How does this interact with an existing apply method?

Would the following still work or now be ambiguous, and what is the rule?


class A(s: String)

object A {

def apply(s: String): A = new A(s)

}

1 Like

So have I understood correctly that this is more restricted than the original proposal?

From the educators’ view, I support this with only minor reservations. (Everything has trade-offs, right?) The fact that object creation sometimes requires new and sometimes doesn’t can be quite confusing for students. I notice it most when we get to the point in the semester where we start doing graphics with ScalaFX. For whatever reason, most of the ScalaFX classes don’t include def apply in companion objects for the normal use cases. The result is code that contains a mixture of using new with not using new and the only way to know which one is correct is to try them both or to dig into the API. At that point, the students aren’t really prepared to dig into the API and see if the companion object has a def apply method on it. I think that this would remove a lot of confusion in that situation.

My one reservation is that when they do move to Java and C++ I wonder if it will add one more little bump to the learning curve. That’s a minor issue, but it is the type of thing I think about. Given how many schools are starting with Python and they don’t care about this I will assume this wouldn’t do anything to slow adoption of Scala in colleges.

Now a question, how will this syntax will interact with the creation of anonymous inner classes and classes that take no arguments. It isn’t uncommon to have code of the form new ClassName { ... } that creates an anonymous subtype of ClassName with a body that comes from the curly braces. The proposal here only addresses situations with the form f(args). Given that the curly braces can be used for an argument list with a single argument, I worry that there is going to be ambiguity here and just writing ClassName { ... } isn’t going to work. Perhaps ClassName() { ... } would, but it isn’t immediately obvious to me so it would be nice to hear from someone who has a clearer view of how these features will interact.

1 Like

After reading the previous comments, I would be tentatively in favor if new f(args) is deprecated except for creating a (new) anonymous subclass. Although maybe a solution where new doesn’t have to exist anymore would be better.

One thing I find inconsistent: class Foo does not seem to introduce a term name. But you can use the name Foo where normally only term names are allowed.

1 Like

You still need new for anonymous classes.

@curoli If there is an apply method it takes precedence.

3 Likes

Thanks for the clarification!

In that case, I like the proposal - I already found myself routinely creating apply methods.

1 Like

Yes, please!

I have so much pointless boilerplate with companion objects forwarding an apply, unchanged, to the constructor.

Refactors from case classes to non-case classes (because I need inheritance, for instance) are more of a pain than they need to be because I have to go around adding new all over, or I have to write said apply method.

Same deal with refactors the other way.

You create objects without new all the time (collect, .get on a Map, foreach on an Array[Char], (x, y), you name it). new for class instantiation serves no useful purpose except to remind people of things that they don’t need to be reminded about.

6 Likes

Thank you for that response @odersky. At the CS2 level and above I can see that as being a good thing because it makes it clear that they are doing more than just creating a new object. At the CS1 level I don’t think they are ready to hear about anonymous inner classes so I’ll have to think about that some. It might change the style of code that I write use for ScalaFX applications both in class and in the next edition of the textbooks.

The reality is that people attach case modifier to a class just to avoid writing new when creating an instance of it. Because of that there are case classes that aren’t immutable data carriers, but are side-effectful services where generated hashCode, equals, unapply, etc don’t make sense and aren’t used at all. Blurring the (new vs apply) distinction between ordinary classes and case classes make such abuses much less atractive. Therefore I support implicit new.

5 Likes

I’m wary of this change, because making constructors visually equivalent to factories makes it more likely that factory-like code will be written in constructors. Why bother going to the trouble of creating a companion object and apply method when you can just toss another def this(...) in the class body and get the same syntax?

This is a problem because using factories limits the chance of trouble from doing real work in constructors or leaking references to partially constructed objects.

5 Likes