Meanings of underscore (including wildcard imports)

We could replace asInstanceOf[C] by just as[C]

Yeah that’s a good idea. Martin is already thinking of a new keyword as, an except keyword (or similar) would make sense. I’d suggestion slightly different usage though. How about this?

import java.time as jt
import java.lang.*
import java.util.* except Date
import java.net.* except {URI, URL}
import remote.components.{Comp as C, CompContainer as CC, ToggleableComp as Tc}

Seems like the Comp can still be referenced with as. I have no issue with => cause it an one-direction arrow.


Dart is using show and hide

I’d like to make a formal proposal to change import syntax and would like to get your feedback on it. The proposal as follows:

  • change the wildcard specifier to *
  • change the renaming specifier to as
  • allow toplevel renamings and typed given imports without requiring braces around them.

Preface: This would be the last language change for 3.0. We are done for Scala 3 and very close to the first release candidate. This proposal should not delay the roll-out. There is a full PR which can be merged at any moment. This also means that feedback has a higher chance of being relevant if it is given quickly.

Reasons to change wildcard imports:

  1. Orthogonality. We have changed meanings around elsewhere so that * means “all” (in varargs), ? means “some” (in wildcard types), and _ means “parameter to be filled in” at the use site and “unnamed/ignored” at the definition site. The change to wildcard imports is needed to get full alignment here.
  2. Familiarity: With * for “all” and {...} for “choice”, import syntax exactly mirrors file specifiers, so it is immediately known. Besides, everybody else uses * for wildcard import. There’s no need to be different for difference’s sake (the fact that * can also be a symbol to import is not enough reason to be different).

Reasons to use “as” for renaming:

  1. Clarity. as is immediately obvious, whereas => is again overloaded in the wrong way (i.e. the binder of an => appears usually on the left, not on the right).

  2. Orthogonality. With as we can have the simple rule that every import specifier can be used by itself or with others in braces. With => we can’t do that, since

    import p.q.foo => f
    

    would just look too weird. Compare with

    import p.q.foo as f
    

    which is crystal clear and very familiar (lots of languages do it that way).

Migration:

Both old and new syntax are supported in 3.0. At some later point, when cross-building with 2.13 is no longer supported, we will deprecate the old syntax. Of course, rewrite rules are in place to rewrite to new syntax automatically on demand. At some even later point the old syntax will no longer be supported, but there will always be specific error messages and rewrite possibilities to deal with it. I.e. the error message would say something like “this syntax is no longer supported, it’s now written like that, do you want me to rewrite it?”

Why Now?

Scala 3 is a rare discontinuity in language development. There won’t be another discontinuity like this for at least 10 years and maybe ever. The only time one can change something as pervasive as wildcard imports is at such a discontinuity.

Why So Late?

It simply slipped through. Imports are not a central language element and everybody was used to the status quo so that nobody put it in question. It was @lihaoyi who proposed the change to wildcards recently in a different thread. I think if we had made the change a year ago it would have been completely uncontroversial.

Why Not More?

In the discussion we also discussed a different operator for suppressing imports such as hiding or except. This would be quite a different change, more complex than the simple renamings that are proposed here. So it would have to come later, and since it is a more specialized change it could also come later. In my opinion, once the different meanings of underscore are sorted out as is proposed now, keeping x as _ for exclusion might work well enough.

We also might want to use as in different roles (e.g. as a binder in patterns). That’s again a separate discussion to be had later. In any case, as is a soft modifier, so it can be continued to be used without restrictions as a regular identifier.

Is this more of the optional braces wars?

No. With optional braces we decided to be different from the Java tradition for the sake of legibility. That was a difficult decision. The main change proposed here is to conform to the universally accepted standard on the JVM and largely elsewhere. So the change, once implemented, will be immediately accepted. There’s some inertia now since most people are opposed to change but my prediction is that it will dissipate very quickly.

But it makes Scala look different!

Yes. Scala 3 is striking a balance between being a new language and one that is still very recognizably Scala. So, for Scala 3 it’s OK to be different if different is clearly better. I believe in this case the change clearly is better. And we should not refrain from improving aspects of the language just because an area is deemed not very important.

26 Likes

The * in varargs does not mean “all”, but “an arbitrary number of it”, just like in regexes. That is not consistent with making * a wildcard in Scala.

Not a huge deal, but definitely not a “needed alignment”. The motivation behind this and other changes is rather obscure.

4 Likes

a, so

import scala.concurrent.{Channel => _ , _ }

will become

import scala.concurrent. Channel as  _ , *

?
If yes - ok, looks acceptable for me.

No it would have to be

import scala.concurrent.{Channel as  _ , *}

You need braces if there is more than one specifier, to avoid ambiguities.

It looks like a great explanation and balance of concerns. Tysvm for persisting.

Previously it was “add braces on multiple imports OR 1 renaming import” now it’s just on multiple.

Btw, picking * and as for imports mirrors Rust’s use declaration syntax: Use declarations - The Rust Reference. Also note how Rust allows for nesting for braces, which is a surprising omission in Scala seeing how everything else nests almost anywhere.

3 Likes

Looks great to me. Also, big +1 on this being the last change to the language for 3.0.

4 Likes

In and of itself, I think this is a real improvement. It’s quite late, but provided the old syntax still works for the time being, and deprecation doesn’t begin until at least 3.1, I don’t see any problem with it.

1 Like

I think it makes the language slightly less regular but slightly more familiar to those coming from other languages. And it’s yet another change to handle.

I modestly dislike it for these reasons, but it’s not a big deal.

import foo.bar as baz is nice.

import foo.* is okay.

import foo.{bar as baz, *} is worse than what we’ve already got, because * doesn’t mean “every”, but rather “match that which hasn’t already been named” which is what _ is supposed to mean. Note that Rust has the same syntax, but if you write use std::cmp::{Ordering as O, *} you also import std::cmp::Ordering along with everything else in std::cmp because it really is a wildcard.

Example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=59a6c88eb2d5271deca4d9b69d21a6a9 (note…this also shows how Rust has rather pedantic f64 sorting, hence the need to talk about orderings).

For Scala,

import foo.* with bar as baz

would be fine, though.

Anyway, it doesn’t much matter, I don’t think. It’s a small part of the language and we can learn anything that small, and renaming is nicer. Leaving it as is is more comfortable and more regular. Fine either way.

4 Likes

This, x100.

When Scala 3.0 is released, I look forward to a long period of syntactic stability, and a significant period of time where the only changes to the language are backward compatible. Ideally, all of 3.x.

Updating tools and books and videos is a herculean undertaking. And we don’t want to do that right after Scala 3.0 is released. Whereas, any last minute changes to the language right now may understandably upset those looking forward to Scala 3, but as virtually no production code has been shipped on Scala 3 and tooling is still in varying states of disarray (very little works fully on Scala 3), it has comparatively little commercial impact.

Others equally well-informed may come to different conclusions, but my own subjective opinion is that now is the time to change syntax, if it’s going to change at all for the foreseeable future.

As for the proposed import syntax, I think it’s a notable improvement, though I’m not that concerned one way or the other.

6 Likes

If the choice is: make the *-import syntax change now, or not at all in the next decade, Im +1.

I think aligning with other languages is a win. Many, perhaps most, engineers need to be polyglots, and aligning with convention in other popular languages (esp. python) reduces the mental effort when jumping between languages.

1 Like

I’m in favor of the * wildcard change (I’d have to be, since I proposed it!) and am neutral on the as change.

I do think as does look nicer, and renaming with braces is certainly nice as well, but it’s balanced out by the churn it adds to the language. Overall a wash, and given how infrequent renaming-imports are in the code I work with it’s probably not a huge deal.

I will repeat though that we can’t keep this churn up forever. Scala3 is already supposed to be months into feature freeze, so the choices are either (1) we lock down now and begin rigorously polishing (both implementation and spec) in preparation for release (2) we postpone the release to allow more ideation and lock down later or (3) we ship a half-baked language with a bunch of poorly-thought-through last minute changes with problematic interactions and unforeseen consequences that we’re stuck with for the next decade.

Those are the paths that lie ahead whether we like it or not. While I’m neutral between (1) and (2), (3) is equally likely if we’re not careful, and is something we need to avoid at all costs.

17 Likes

What it upsetting (for me, at least) is not the changed per se, they might be good (they even are).
What is upsetting is not recognizing that such changes

1/ might also be in contradiction with scala 3 other goals, like “it’s not a different language than scala 2” and “there’s an ease migration path” and “the language get simpler and more regular” (and so, assessment/revision of these goals might be needed),

and 2/ they factually mean that we were not in feature freeze in the last months, and that more time will be needed to assess the new language balance - and accept that if the balance is not right, perhaps MUCH MORE TIME will be needed to adjust it, especially if that’s the “last scala syntax for the decade”.

And actually, @lihaoyi told it much better than me here: Meanings of underscore (including wildcard imports)

3 Likes

While change _ into * as wildcard seems reasonable, I don’t feel the same for => into as.
Even in the example above it feels odd, as _ doesn’t feel as discarding but rather assigning Channel to the _ as a placeholder. With => I was always reading Channel => _ as “Channel into void” :thinking:.

1 Like

We decided to target the end of this week for the RC1 release of Scala 3.0. The change of imports will be part of it. Thank you to everyone who provided feedback!

12 Likes

Hello, I’m pretty sure it was talked about recently and/or that there is a place where it’s documented, but I can’t find it back, so anyone would mind to point me to what it the foreseen planning for scala 3 (RC, etc) ?