Proposed syntax for _root_

It is no secret that imports are one of the most confusing things about some libraries and there has been a lot of work to simplify this. However, I’m still finding that it’s easy to get confused between relative and absolute paths. How many cats, fs2, implicits or io packages are there now? All the interop imports just make it worse and worse.

We can help ourselves by ensuring we use root-based imports, but it’s painfully ugly!

import _root_.io.circe._
import _root_.fs2._
import _root_.cats._

It’d be nice if we cleaned it up a little bit with a symbol alias for _root_. e.g. | or . or / (pick one)

import |io.circe._
import /fs2._
import .cats._

and at the same time, ban the creation of packages named _root_ so that it doesn’t end up becoming a relative name!

This could even be done in parser (taking care with Position), just naively rewriting import[\w]+[|/.] to import _root_.

10 Likes

Agreed – relative import paths have bitten me hard on the ass a few times (when you do get a name conflict, the errors tend to be very confusing until you figure out what’s going on), but the current _root_ syntax is too cumbersome to happily use routinely.

Personally I like Python’s approach, which would translate roughly to:

package current.pkg
import lib._ // == import _root_.lib._
import .app._ // == import current.pkg.app._
// and maybe also
import ..utils._ // == import current.utils._, and so on

That would also probably be the most familiar for Java developers, since it would leave the behaviour of regular imports equivalent to Java’s.

I would also guesstimate that making import x absolute would be the least breaking way to get rid of the current ambiguous default behaviour.

1 Like

Actually, I disagree: making import x absolute would be enormously breaking – a huge amount of code depends on relative imports. It would likely be auto-fixable, but it would require rewriting a lot, and changing habits deeply.

But @fonmil’s original suggestion, with some more concise and easy-to-use syntax in place of _root_, seems like a pretty innocuous improvement. (Personally, I like the / idea, but I could see a leading . as possibly being more consistent with the existing syntax.)

4 Likes

I think it will be rather confusing, as . is widely used as “current directory”.

4 Likes

You don’t have to disable it immediately, you could make it a deprecation warning and step up the severity gradually in time for Dotty/3, which will be massively breaking anyway.

Taking this one step further, how about . for starting relative imports, and all other imports are assumed to start with _root_. We could then get rid of _root_.

Making import x absolute seems to me to cause more pain than gain—including the change of habits.

More importantly, because of the change in habits, too much planned breakage might slow the transition to Dotty—people are trying to keep lots of things compatible, and there will likely be some unplanned breakage in corner cases.
(I also wonder how soon we can rely on scalafix, but I asked this separately in Scalafix v0.3 released - #2 by Blaisorblade).

How would you do something like this?

def foo(c: Context) = {
  import c.utils._
  ???
}
1 Like

. is widely used as “current directory”.

A dot at the end is also used for fully qualified domain names. As package names usually follow reverse-DNS notation, this is kind of fitting.

1 Like

I would love to have a special, hard-coded, explicit syntax for _root_. Having a weird python-like underscore-marked magic variable that you can still shadow/etc. is just terribly inelegant.

I don’t like the | / . syntax @fommil used above (I think it looks odd, and . would cause hell during parsing when combined with infix operators), but I think some concise, elegant syntax would be exceedingly valuable.

If we could make it convenient, I would love to standardize on always using the _root_/equivalent prefix when importing fully-qualified from packages. That way you could skim a list of imports and immediately see which ones are importing from the root and which ones are importing ad-hoc from each other, v.s. right now you have to construct a symbol-table in your head to see whether import util.blah is importing from java.util, scala.util, mysnowflakeproject.util, or even from some val util = blargh that was defined/imported from somewhere else.

2 Likes

Good point. Perhaps make .x.y.z dependent on the current scope, rather than the current package?

All of these are pretty awkward/confusing. I’m fine with leaving _root_ as a weird name, but if we want to not allow shadowing, we just need to pick a reserved word or an empty symbol. So, import package.io.circe._ or import null.io.circe._ or import ``.io.circe._. Any of these would prevent the issues with rooting. You don’t even need to remove _root_, just advise the alternate form and the root problem will gradually go away.

4 Likes

The obvious choice is $, Excel users will feel right at home! (Sorry…)

More seriously, I agree _root_ would be used more if it was more cuddly, and using it would be the right thing to do. To me a leading . looks OK. Changing the default to absolute is not realistic.

Maybe this is a good moment to bring up the old idea of an export feature? Here’s a pre-sip from 2012 by @rssh, it has links to discussions at the end.

4 Likes

Using the package keyword seems like the least weird-looking alternative, but I would remove the . after it:

import package io.circe._, scala.collection.mutable
import package util._ // error: object util is not a member of package <root>
import util._ // ok... but perhaps warn against it when at the top-level of a file?

Using a leading . feels pretty awkward and would contribute to the uglification of the Scala syntax IMHO.

2 Likes

I like the package keyword including the .:

import package.io.circe._, scala.collection.mutable
import package.util._ // error: object util is not a member of package <root>
import util._ // ok... but perhaps warn against it when at the top-level of a file?

Whether we make it mandatory or not, I think it looks better than _root_ despite being slightly more characters.

One advantage of including the . after package is that we could then allow package.foo.Bar.doSomething(...) as an expression, whereas the syntax without the . makes it more special-cased to only import statements.

3 Likes

:+1:
One question: does package. on the first example automatically prefix itself to the following imports, so that it is

import _root_.io.circe._, _root_.scala.collection.mutable

?

One question: does package. on the first example automatically prefix itself to the following imports, so that it is

import _root_.io.circe._, _root_.scala.collection.mutable

No, since the dot operator has a higher precedence than the comma
But braces would provide this functionality.

import package.{io.circe._, scala.collection.mutable} would be shorthand for
import package.io.circe._, package.scala.collection.mutable

Just to be clear, this is more “could” than “would” since it does not currently work:

scala> import _root_.{scala.util._, scala.io._}
<console>:1: error: '}' expected but '.' found.
import _root_.{scala.util._, scala.io._}
                    ^

But of course, if we’re discussing changing the syntax to let import package.foo work, there’s no reason we wouldn’t be able to change the syntax to make this syntax work too

1 Like

As all this discussion is mainly focused on syntax, I think that allowing something like import package doesn’t make a lot of sense. I would actually prefer import absolute, making it clear to the reader (who may or may not be familiar with the syntax) what that line is for.

That would result in:

import absolute io.circe._, scala.collection.mutable
import absolute util._ // error: object util is not a member of package <root>
import util._ // ok... but perhaps warn against it when at the top-level of a file?

The proposed syntax misleads users. What does import package means when you could use it for wildcard imports as well as concrete name imports?

This is true. My proposal has no way to deal with this. Though I see value on using an absolute import over an expression – after all, if you use an absolute import inside blocks and entities’ bodies, you better make it explicit so that it’s clear that it doesn’t reuse a local symbol.

3 Likes