Proposal: Implied Imports

Do you categories cover scala.collection.JavaConverters?

Do you categories cover scala.collection.JavaConverters ?

Yes: they are extension methods, so will need no implicit support in the future.

@Matveev:

Third party serialization packages are typical examples of orphan instances. They require import implied.

It is very good news, is there any description of it.

IIUC

Ok, It is important to know.

Although It is a pitty that such popular library like anormsql can be considered as rare case.
Why cannot it be simplified as extension methods?

Again, can someone please explain the motivation behind the import implied semantics. Even if I agree that import should not import implied instances, why doesn’t import implied just import all instances including the implied ones? I see no viable use case for just a import implied A._ statement if it imports just implied instances in A (and even if we found such a use-case, we can always just created object implicits with the relevant implied instances).

Additionally, what will happen to the implicit conversion inside Predef? Will the Predef object be imported by default to include the implicits?

On a related note, was a syntax introducing a separate import selector instead of a whole separate form of import clause considered and rejected?

I mean something like

import some.path.{_, implied _}

instead of

import some.path._
import implied some.path._
2 Likes

I personally do not read the import section.
For me, the main idea will be to demonstrate library users that it is not good(rare case).

Someone can make checkbox which automatically insert implied keyword.

I am personally think that library user should not be punished. Such complication should be done only for library authors. But after all I agree that import implied is evil, so It does not matter how more\less it will be with implied keyword.

I only hope that someday there will be more abilities to avoid such cases(for example sql, orm).

1 Like

This could actually be an interesting alternative, since it allows a refinement (which is based on a proposal by @liufengyun). We can also allow something like this:

  import some.path.{implied for Ordering[_], _}

The semantics would be that the import classifier implied for T would bring into scope all implied instances of some.path that return values of type T. The short hand form implied could stand for implied for Any. What’s nice about it is that it does not just discourage misuse but also encourages virtue, where being virtuous means narrowing down your imports to specific members.

We already have wildcard imports and named imports. Named imports are “virtuous” whereas wildcard imports are considered “sloppy”, so we encourage to use named imports. But for implicits, named imports don’t work. In Scala 3, implied instances can be anonymous, in which case we can’t use a named import for them. Even in Scala 2 implicits often have intentionally obscure names in order not to risk name conflicts, which makes it very hard to remember those names for an import. So most people just use a wildcard import instead.

With this new feature, we could specify imported implicits by the types they implement, which is precisely what we care about.

2 Likes

While combine the imports is more succinct, make them separate might be more readable.

import a.path._
import b.path._

import a.b._ for Ordering[_], String
import b.c._ for List[_], File

Can we use it for extension methods too? import a.b._ for T also import extension methods for the type T. Otherwise, it will be difficult to find where do they come from, just as implicit classes.

If this is a new form of import selector then the ability to use it grouped together with others or separately comes naturally, doesn’t it?

1 Like

It is useful to prevent name clashing.

Why do not you consider the means to automate imports.
Actually imports are boring and most duplicated code at least in my practice.

If you care about readability it is important to consider when it is needed. I think it will be really important only when errors occur. In such cases the good error messages is much more important and can solve most troubles.

Why is it impossible to really help to get rid of such wasteful work as doing same import blocks again and again …

1 Like

In the current implementation, I can import implied something that isn’t implied, and doing so has no effect. Is this working-as-designed, or a bug?

scala> object O { def x = 3 }
// defined object O

scala> import implied O.x

scala> x                                                                                                                
1 |x
  |^
  |Not found: x

scala> the[Int]
1 |the[Int]
  |        ^
  |no implicit argument of type Int was found for parameter x of method the in object DottyPredef

I had expected instead that either:

  • the compiler would refuse the import
  • the compiler would accept the import, and add O.x to implicit scope

For neither to happen was surprising to me.

And then conversely, if I do a regular import of an implied thing:

scala> object O { implied x for Int = 3 }
// defined object O

scala> import O.x

scala> x                                                                                                                
1 |x
  |^
  |Not found: x

Again this seem strange; I would expect either a compile error, or for the identifier to be brought into normal scope.

3 Likes

Good point. These should be both be errors.

The proposal and its implementation have been extended with a new construct, which allows to specify implied instances by type rather than by name. Here’s the new text:

Importing By Type

Since implied instances can be anonymous it is not always practical to import them by their name, and wildcard imports are typically used instead. By-type imports provide a more specific alternative to wildcard imports, which makes it clearer what is imported. Example:

import implied A.{for TC}

This imports any implied instance in A that has a type which conforms tp TC . There can be several bounding types following a for and bounding types can contain wildcards. For instance, assuming the object

object Instances {
  implied intOrd for Ordering[Int]
  implied [T: Ordering] listOrd for Ordering[List[T]]
  implied ec for ExecutionContext = ...
  implied im for Monoid[Int]
}

the import

import implied Instances.{for Ordering[_], ExecutionContext}

would import the intOrd , listOrd , and ec instances but leave out the im instance, since it fits none of the specified bounds.

By-type imports can be mixed with by-name imports. If both are present in an import clause, by-type imports come last. For instance, the import clause

import implied Instances.{im, for Ordering[_]}

would import im , intOrd , and listOrd but leave out ec . By-type imports cannot be mixed with a wildcard import in the same import clause.

1 Like

Is that requirement intended to emphasize underlying order of evaluation semantics? I see no obvious syntactic reason other than that.

I think it’s to avoid the confusion around the comma: is it separating a list of types, or does it introduce another import. Consider swapping the order: import implied Instances.{for Ordering[_], im} – should im be parsed as a type (i.e. without ambiguity, you meant for Ordering[_], for im), or is it a term name?

That has less ambiguity, but it’s still kinda ambiguous because in language, , has lower precedence than spaces. If it were to be “intuitively” clear (i.e. follow natural language rules), it would need to be something like

import implied Foo.{evA, evB}
import implied Foo.{for C, D}
import implied Foo.{evE, evF; and for G, H}

Even that doesn’t read all that well, though, because C and D presumably aren’t in Foo. And it looks like they are because of the braces. If you wanted a natural language version it would be

import implied from Foo for C and D

which suggests that you have to actually write it like that:

import implied Foo for { C, D }

which doesn’t generalize well to importing instances on the same line.

It’s because a for clause is the analogue of a wildcard import, which also comes last. This is particularly important in conjunction with removals. I.e.

import p.{x => _, _}

means you import everything except x. Likewise,

import implied p.{x => _, for B}

means you import all instances of B except x.

1 Like

This could simplify the imports for libraries like cats.

Currently you need to do something like this to get the instances for Int and List:

import implied cats.instances.int._
import implied cats.instances.list._

It looks like, you’d be able to do this instead:

import implied cats.instances.all.{for Int, List[_]}

I assume it would be possible to adjust the hierarchy to take further advantage of this, but even with the current hierarchy it’d be possible to simplify things.

The for clause details the type of the value being imported, rather than the type it is parameterised over, so you would import these instead:

import delegate cats.instances.all.{for Show[_], Traverse[List]}
2 Likes

That seems like a bug, as it’ll bring in a bunch of stuff into scope that will likely not be used.

Assuming this object:

object implicits {
  delegate for Show[Int] = ???
  delegate for Show[String] = ???
  delegate for Show[Foo] = ???
  delegate for Monoid[Int] = ???
  delegate for Eq[Int] = ???
}

If you ask, “What are the delegate instances for Int?”, the intuitive response would be Show[Int], Monoid[Int], and Eq[Int].

On the other hand, if you ask, “What are the delegate instances of Show?”, the intuitive response would be Show[Int], Show[String], and Show[Foo].

It’s also more useful to get all the type class instances defined for a type, rather than all the instances of a particular type class.

The best of both worlds, would be something like this:

// To import Show[Int], Monoid[Int], and Eq[Int]:
import delegate implicits.{for Int}

// To import Show[Int], Show[String], and Show[Foo]
import delegate implicits.{of Show[_]}
3 Likes