Wildcard imports considered harmful

#1

I would like to bring up the question whether to keep wildcard imports in future Scala versions (dotty).

This problem first came to me while working on scripting. I wanted to use the Scala compiler from a restricted environment (Java web start, servlet engine, Android, etc.) In such context, often you do not have access permission to library jars, so you cannot list all classes present in some package for instance. A consequence is that the compiler cannot make sense of wildcard imports

This was also causing problems with the reflect Toolbox, but macros were reworked in Dotty not to allow such things (and rightly so).

However, the issue was brought back recently in the rework of implicits:

In fact, I’d go a step further and say wildcard imports:

import my.lib._

Are themselves a code smell, implicits or not

As it turns out, big piles of imports are only a problem for implicits, as normal “flat” imports are dealt with by IDEs these days (and they are best practice, for readability and understandability of code).

Why not put the effort on implicit imports automation in IDEs, instead of devising such complicated wildcard imports as is currently the case:

I ask the question (and yes, there is the problem of anonymous implied/delegate/given instances, I do not have an answer for these now, maybe naming them should be mandatory if they are to be imported).

Any thougths ?

Edit: There is also the problem of “implicit” wildcard imports, in the sense that you do not have to import them explicitly. There are 3 of them:

import java.lang._
import scala._
import Predef._

These might be easy to do without, or not, the question is open.

Edit2: We could also consider that all classes in the current package are also wildcard imported, as in:

import ._

On a scripting point of view, this might not be a problem, as we can suppose that all classes in the current package are in the source classpath, that is, with no permission restriction. But this brings up the case where we have nested packages, as in:

package foo
package bar

, which translates to:

package foo.bar
import foo._

I have never been comfortable with this construct, which introduce too much complexity in my opinion, in particular for beginners. So I guess my proposal is also to get rid of nested packages.

#2

You don’t need to use wildcard imports, implicit or otherwise. If they bother you, don’t use them.

Superfluous clutter at the top of every file that can only be managed by an IDE, and even then not very well, is something I find a lot more harmful in most cases.

If you get conflicting implicits and the compiler doesn’t tell you exactly where they came from, that’s a compiler issue. Once detected, the fix is easy enough (e.g. import org.foo.{troublesomeImplicit => _, _}).

If you have missing imports, not having wildcards is only going to make things even harder.

5 Likes
#3

I’d like to point out that this is specifically within the context of the type of project that @lihaoyi works on, and isn’t necessarily representative of the type of projects that everyone works on. I pointed this out on the original thread, which is quoted below for convenience:

Occasionally you need to define-and-import orphan implicits when you need a T[V] and both T and V are from external libraries, but those are uncommon cases and not something that you do “by default”

I think this is more common than you believe. Besides the canonical library of these for Cats and the Standard Library (alleycats ), a trivial search brings up three independent projects providing this for Cats and Scalacheck. There are also additional projects along these lines for Cats & Scalatest, and equivalent projects if you replace “Cats” with “Scalaz” in the preceding examples.

That’s aside from the internal libraries designed to correct ergonomic issues with Scalatest, Scalacheck, Mockito, Anorm, and etc that I currently help maintain in my day job, which depend heavily on implicits to make them usable. Better discoverability and error messages would be very helpful, as currently we rely on an import convention similar to cats.

Making define-and-import more difficult would greatly increase the adoption barriers of these libraries, and removing this capability altogether would put us right back into the space of relying on the library designers to anticipate every use case. Based on the number of libraries providing this functionality, there’s clearly an unmet need which the library designers aren’t able to fulfill.

Some of them are great about trying to provide as many of these instances as possible (shout out to enumeratum, who do an exceptional job of this), but others wouldn’t be able to keep up (scalacheck is a good example of an excellent library with a dearth of maintainers). Regardless, given the number of possible connections between libraries, I don’t think it’s reasonable to put this burden on them in the first place.

#4

Sorry, this is not the part I intended to quote. I do not want to question that one needs to import implicits, but only to wildcard import them (or anything for that matter).

#5

There’s the rub: if you’re importing implicit instances, enumerating each one by name is a real pain in the neck. This’ll be more of a problem in Dotty, where they might not have a name.

1 Like
#6

Yes. Unless your IDE can give you a hand. In addition, it would be done once and for all, removing the burden for the compiler to search for these elements over and over again.

#7

Some people prefer not to have language features that make an IDE a requirement for getting anything done.

Also, even if the IDE helps, the burden of writing, reading, and maintaining code is increased because of all the busywork surrounding semi-manual management of individual imports.

4 Likes
#8

If you are using wildcard imports two or more times within the same scope, how would you even find where something got imported from without IDE?

#9

Why would you care unless the compiler flags an error, at which point the compiler would tell you?

2 Likes
#10

Of course you can not.
But take into consideration that you also cannot do any other cross navigation without IDE.

 val x = getObject
  x.doSomeThing() // how would you even find where the method is declared?

IMHO: Import itself can be considered as boilerplate code in some high level areas
So wildcard imports can be just less evil.

#11

I agree that for anonymous instances, we still have to come up with something. However, if I understand correctly, this is only a problem for orphan instances, which we could require to have a name.

#12

While it’s more of an issue for anonymous instances, it’s still an issue for named instances. Who really wants to maintain 20 lines of imports from the same file when a wildcard would provide the compiler the same information?

It’s tedious, and distracts from actually getting work done.

1 Like
#13

Because it helps understanding the code ?

#14

How would you understand a piece of code without understanding what those things in it are?

#15

I haven’t found a case where this:

import cats.instances.vector.{
  catsStdInstancesForVector,
  catsStdShowForVector,
  catsKernelStdOrderForVector,
  catsKernelStdMonoidForVector,
  catsKernelStdPartialOrderForVector,
  catsKernelStdHashForVector,
  catsKernelStdEqForVector
}

Provides more useful information than this:

import cats.instances.vector._

Fun side note: the more verbose version is actually harder to use, because it flattens the implicit scope so you can’t take advantage of the nicely layered priorities setup by the library maintainers.

3 Likes
#17

How does having a list of names help you understand it?

Types are far more informative, and you get those without needing the imports: at the use site with an IDE, or in error messages from the compiler. Even with no editor support at all, it takes a few seconds to drop a : Nothing on any variable you please and the compiler will helpfully yell the type back at you.

3 Likes
#18

I find wild card imports very useful. They may well be a problem when using large numbers of independently written libraries, but that is not the only way of writing Scala code. I try to think quite carefully about naming in the packages that I’m wild card importing, so naming conflicts very rarely occur and are removed, when they do. From this perspective it is selective imports from a package or even an object that are code smells.

2 Likes
#19

If I have the full name (x.y.z.Thing), I can look it up in the ScalaDoc, or at least know which library Thing came from.

To recompile one file, I would have to have all dependencies in place. Basically, tell my build tool to compile only one file. And somehow also tell my build tool that all files depending on the changed file are not out-dated, because I’m going to cancel the change. Not sure which build tool supports that.

All of this assumes, of course, that my project is in a buildable state to begin with.

Let’s say I’ve been using Thing and after upgrading dependencies, it no longer is where it was. If I had imported it using import x.y.z.Thing, I could easily see that x.y.z.Thing no longer exists. If, however, I used import x.y.z._, all I would get is that Thing is unknown, with no hint where it used to be.

Also, consider:

import frodo._

import samwise._
import peregrin._

import meriadoc._

import gandalf._

import aragon._

import boromir._

import gimli._
import legolas._

val usefulThing = new Thing

What is the full name of Thing here? Could be just Thing. Or frodo.Thing or samwise.Thing or peregrin.Thing, etc. Or frodo.samwise.Thing or frodo.peregrin.Thing or frodo.samwise.peregrin.Thing. I’m counting 512 possibilities what the full name might be.

#20

That makes at least overuse of wildcard imports harmful.

#21

So use jump-to-definition in IJ, or whatever ENSIME offers, or write val usefulThing: Nothing = new Thing. This is not dissimilar to Java wildcard imports, other than that they can be relative – the “problem” still exists, although there are fewer possible resolutions.

That would be an excellent feature.

Is this thread a proposal to remove wildcard imports, or are we just bellyaching over a feature anyone’s free to not use?

3 Likes