Meanings of underscore (including wildcard imports)

I think that’s circular reasoning. Other languages (e.g. Python, Go) use renaming imports widely. They are arguably safer and clearer than the wildcard imports which are overused in Scala. At least one reason why renaming imports are not used more widely in Scala is that the syntax is not as clean as it could be. Put yourself in the shoes of an educator. You want to teach idiomatic imports. Do you really lead with

import java.{util => ju}

? No, there’s just too much weirdness to explain, you won’t have time for that. Whereas if it was

import java.util as ju

things would be different. You’d probably think that needs to be taught from the start.

These things have snowball effects. A small nudge can lead to a widespread change in coding styles down the road.

To address the concern that this introduces new special cases: The new syntax is more orthogonal than the old since every import specifier than can appear in curlies can appear outside it. Curlies are only used for grouping.

3 Likes

They are arguably much less clear than using things’ original names. Unless a file is so short you can see the imports all the time, renaming imports are a last resort

4 Likes

I’ve written a lot of Python, and the only places which use renaming imports heavily is the Scientific Python space. They aren’t used heavily in sysadmin work, in learning programming, in backend systems development, or in any of the other fields. Apart from import numpy as np and import pandas as pd, it really isn’t all that common. I can probably count the commonly-renamed package imports on one hand.

Put yourself in the shoes of an educator. You want to teach idiomatic imports. Do you really lead with

I’ve written a book on Scala and never needed to touch renaming imports at all. It just isn’t all that important, not for getting started, nor for working in our million-line codebase at work. Spelling out java.util.List once in a while really isn’t a big deal. In the rare rare case where renaming something helps with clarity, the syntax really isn’t that awkward. Not great, but not terrible.

To me, this is an excellent reason to slow down, ensure there’s broad discussion about what change we want to make, and thoroughly explore the solution space. Exploratory work and speculative improvements are fine, but if we want to ensure quality in the language and consensus in the community it can’t just be “Martin thought it’s a good idea on friday and was in a hurry, therefore we’re gonna live with this for the next 10 years”.

These discussions take time, and there’s a lot to discuss, hence my proposal for a Q4 release date. I think the fact that there’s literally zero support in this thread for pushing for this kind change at this point in the Scala3 release timeline speaks volumes. If you want to disregard the feedback and forge ahead alone, you have the commit access to do so, but if over-used that’s a recipe for leaving the community behind.

14 Likes

I’m skeptical about this claim. In practice, I rarely see a desire for renaming imports, because in a substantial codebase they’re just plain confusing – they allow something to have one name in this file and a different name in another. They’re generally treated as a necessary evil when you have name conflicts, but the “evil” isn’t because of the import syntax, it’s because it produces sub-optimal code when considered in its larger context…

4 Likes

It is not always so.
I think more rare code should be more intuitive. It is very annoying when a seldom piece of code force you to dig up the documentation.

1 Like

The idiomatic use of renaming imports is as a replacement for wildcard imports. The argument is that

  import longPackageName as lp
  ...
  lp.fooBar

is clearer than

  import longPackageName.*
  ... 
  fooBar

even if lp is short. fooBar could come from anywhere, but with lp.FooBar we are explicit from where it comes.

This argument was promoted by Niklaus Wirth in Oberon, which only had renaming imports but no wildcard imports. That omission was intentional (Oberon’s predecessor Modula-2 still had wildcard imports). I believe Go has a similar philosophy, at least I read that the “dot imports” which seem to play the role of wildcard imports should be used only for testing.

But arguing how common renaming imports are or should be is a sideshow anyway.

3 Likes

As a counterargument to renaming imports never happens, now and then I work with stuff that has versions in the package names. Renaming helps clean up stuff a lot there for me.

I also find that renaming imports is very useful when you have two very similar type names coming from two different places.I could just declare one of them the “true” type I’m working with, but I often find that the code is much clearer if I can write out where X and Y came from.

1 Like

Why I’m using renaming imports:

  • I once called an old Java API that used java.io.File, which I renamed to JFile to avoid conflict with better.files.File

Why I’m using wildcard imports:

  • When I ask IntelliJ to “optimize imports”, it puts all imports form the same package into one line, so I’m using wildcards before the line gets too long

  • Some Scala library needs some very hard to guess implicits imported, and the docs just use wildcard imports

One interesting move would be to remove (deprecate) wildcard imports, not just rename these.

2 Likes

Import renaming was how I convinced the tech lead at a start-up to let us use Scala-as-a-better-Java because it saved us having to type fully qualified class names in some translation code. I currently use it where SHRINE medical records queries flow in and out of SQL databases, and will be using it a lot to translate in and out of HLA7 standards eventually. It’s not an every-day thing, but it helps solve an uncommon working-man problem. The problem and the renaming are very high-profile, and are a great selling point.

I rarely use wildcard imports on the job unless I’m just trapped in an implicit maze and have nothing better than example code that imports whole packages. With some kids I teach I’ve use them for little DSLs. I like the idea of a two-letter import rename better, but that’d be irritating when we’re defining our own symbols. I’ll likely still use them for importing things like math for angles.

1 Like

If renaming imports are a sideshow, then that argues for leaving them alone, considering how many other plates are in the air for 3.0. It’s only worth making past-the-last-minute changes if they are actually important.

7 Likes

I think seen in isolation the change to _ and the change to => are both disputable. But if we do one now, we should do the other as well. If we clean up import syntax, we should not stop half way.

Both replacements are uncontroversially better, and very easy to do. We cannot do the change to wildcard imports later, now is the last time to do it if we want to do it at all. We might be able to change => for as later, if we decide to leave wildcard imports alone.

So the choices we have are:

  1. Do both changes now,
  2. Do only the renaming from => to as later, and leave wildcard _ alone
  3. Do nothing
1 Like

Personally I like as for imports. It doesn’t solve the bigger problem of the name selector being weird, but it does make the one-liners more friendly.

However, I don’t think the precedence of as should be higher than .. So

import scala.collections.mutable.HashMap as HM, ArrayBuffer

should be a no-go. It looks more like HashMap is both HM and ArrayBuffer.

I would love for as to be a more general renaming tool, though we have to be careful with the syntax that we don’t collide with the name of collections of a. (xs is what you call many x, so as is many a). I’m not sure we have time to consider this. Or even to do the as on imports. But I think it moves things in the right direction, unlike * instead of _ on imports. (It’s less regular within the language, requires a lot of change, and isn’t much of a pain point already.)

2 Likes

I’ve never ever had problem with old syntax. It was always clear to me, and I have NEVER forced to google it (And there is a lot of features where I did it a lot: :_*, implicit def, for, () vs {} even match).
Arrow is used in unique context (line always starts with import) and this clearly distinct it from functional usage.

//For me this is visually much cleaner:
import remote.components.{Comp => C, CompContainer => CC, ToggleableComp => Tc}
//than this:
import remote.components.{Comp as C, CompContainer as CC, ToggleableComp as Tc}

I just like the old arrow way because it is much simpler to pars visually imports.

8 Likes

I agree that * could be better for wildcard imports but, changing it right now seams to be unnecessary risk (this will touch almost EVERY FILE in EVERY PROJECT). If we rally wan’t to change it we could:

  • deprecate import somelib.withStarObj.* in favor of import somelib.withStarObj.`*` in 2.13.x
  • use both _ and * in 3.0
  • and deprecate _ in 3.1
1 Like

Assuming that tooling can help with migration then I vote for both, but think that the old syntax should be supported (but give deprecation warnings) in 3.0.

If most other languages use “*” then Scala using the same character makes the language a bit easier/more familiar.

“as” is better than “=>” because it gives more obvious information about what it is doing.

I’m not sure about renaming imports generally since there may not be consistency about how the shortened names are used. However, I could see how I would prefer to import scala.collection.mutable as mut or maybe even just m.

1 Like

While it is true that most other languages use * as a wildcard, Scala currently uses _ as the only wildcard both in imports and other places. Changing Scala from using only one thing as a wildcard to using two different things as a wildcard does not make the language simpler.

And while _ in Scala is not always strictly a wildcard, it is arguably always used in a sort of wildcardish sense (“a hole” according to Martin). To me, every use of _ can be understood to mean “unspecified identifier”, except maybe f _.

On the other hand, introducing * as a wildcard will never achieve the same level of consistency, because

  • * will never be the only wildcard. There will always be another wildcard, be it _ or something else
  • Not every use of * will be as wildcard or something similar. There will always be very different uses of *
8 Likes

“Import everything” is a lot more similar to “bind anything” than “this argument can be supplied 0+ times,” so underscore makes much more sense and is much more consistent than asterisk.

3 Likes

RegExp is not the only notation.
There are standard wildcards.

1 Like

I’m talking about consistency within Scala. IIRC someone made the argument earlier that wildcard import would be consistent with varargs to use * because of the similar meaning but I’m disagreeing with that.

BTW even though in most places other than Scala the wildcard is asterisk, underscore is used as wildcard in at least one popular language, SQL, at least in LIKE patterns. Not that that’s the first thing I’d reach for to copy from but it’s there…