Proposed syntax for _root_

How is absolute better than _root_? It can be shadowed just as much. package is a keyword, and this is why it can work with package.

1 Like

import package.foo.bar.baz means you’re importing from the package foo the path bar.baz, and not some random foo that happens to be in scope.

Given import package.foo.blah, any blah after the initial import package.foo behaves just like any other import. If you’ve ever imported something in Scala, this behaves the same way. package is just an alias for _root_ that won’t get shadowed and looks less ugly (?).

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.

I don’t really understand what this means. If you use package.foo.Bar.doSomething(...), the package keyword makes it abundantly clear you’re not using a local symbol. In fact, it is clearer than having some import 1000 lines up at the top of the file and then trying to figure out which Bar you have in scope to call doSomething with.

1 Like

Well, no one is preventing us from making import absolute reserved. I guess reusing package has its benefits over this approach, but it also leads to the increase of overloading of package, we already have package and package object, and the new one will be slightly different in semantics.

Fair enough, I didn’t read it this way. In my opinion, import absolute is more intuitive, whereas import package has two advantages:

  1. Reuse of package.
  2. Use of absolute imports in expressions.

I guess just because of 2), it makes sense to go for import package.

Nevermind, this is a personal preference.

One note: import package._ will need to have the same semantics as import _root_._:

scala> import _root_._
<console>:11: error: _root_ cannot be imported
       import _root_._
              ^

Depending on the implementation, this may or may not be the case.

I see potential here for a SIP. I think importings terms and types by their FQNs is so important that they deserve better syntax than _root_. Currently, all imports in Scala that are not prepended by _root_ are relative.

Would @lihaoyi or someone else in this discussion like to join me in writing up an official proposal?

1 Like

I have a simpler proposal: do not include contents of current package in default scope. It would be very easy to migrate from old code that sometimes uses members of current package without import - just add a wildcard import at the beginning of file, ie:

package a.b.c
...

becomes:

package a.b.c
import a.b.c._
...

If we want less impact on source code then we can introduce some shorthands like:

package a.b.c with import
...

which import everything from current package (as it is now).

I think this simple change would remove most of the conflicts and probably render _root_ quirk unnecessary.

A variation of above solution would be to remove only subpackages of current package from default scope.

I think the import package idea could work. I find

import package scala.collection.mutable

slightly more pleasing to the eye than

import package.scala.collection.mutable

But either would be an improvement over _root_. I think it would be good to start a SIP about this.

4 Likes

I think _root_, although not the most aesthetically pleasing, is still the clearest, least confusing way to convey that you’re talking about a FQN. Why not just make it a reserved word?

Honestly, is package.foo that much better than _root_.foo? It’s more characters and its meaning is less obvious.

1 Like

I agree that import package com.example looks slightly better than import package.com.example; however, I think @lihaoyi makes a good point about the benefits of the .

2 Likes

The package qualifier could have highest precedence, though that may look a little weird.

println(package foo.Bar.something(...)+1)

// if this ^ looks too weird, the syntax could require parens:

println((package foo).Bar.something(...)+1)

There is precedent for high-precedence whitespace+operator syntax, such as with method lifting –– for example, see f _ andThen g _.

I’m not too keen on package as the special keyword here… firstly it is longer, and secondly if it is being used to designate FQN imports then it would look weird to have import package path.to.Classname.

I like Li Haoyi’s suggestion of a leading dot.

2 Likes

I don’t love the new package usage. It’s one more thing for new Scala programmers to learn. _root_.foo.bar.baz is actually a pretty good solution in my mind, except by avoiding taking a new reserved word it changes root.foo.bar.baz (which I don’t think many people would mind) with _root_.foo.bar.baz which technically is pretty similar but aesthetically I think just puts many people off and won’t be used in a widespread manner. It’s not like anyone actually finds it confusing, they just find it ugly.

I can already see the conversations…
New user of import package foo.bar: “What does import package mean?”
Me: “It means import from root”
New user of import package: “Well why didn’t they just say import root then?”

I can see a couple of alternatives that I haven’t noticed being proposed yet so I’ll toss them in the ring.

  1. Could we take root as a keyword, even if only following import?
    Import root.foo.bar.baz

  2. What about an alternative but already widely known syntax that is new to imports but immediately familiar to most developers while deprecating the old style?
    Import /foo/bar/baz (import from root)
    Import bar/baz (import relative)
    Import foo.bar.baz (import old style)

  3. Make root imports the default and add a special indicator for relative imports, (maybe hide it behind a compiler flag, I think I could come up with a scalafix for old code to make it compatible once the flag is set). The thinking here is that if we actually want root imports to be the common case, with no syntax tax, then add the tax onto the relative imports.
    Import foo.bar.baz (this is the same as _root_.foo.bar.baz)
    Import ~.bar.baz (relative import from whatever is in scope) I’m not attached to the tilde here, the idea is to make the less preferred syntax uglier, instead of making the preferred usage uglier as import package or _root_ do.

1 Like

I second @fommil. Would leading _ be a better fit?

import a.b.c
import _.a.b.c
import a.b.c._
import _.a.b.c._
2 Likes

I like a leading _ to mean relative import, but don’t like it to mean root import. _ meaning root is a bit non-obvious to me but _ meaning whatever is in scope, well that seems to fit the existing usage closely enough.

1 Like

I would prefer using a leading . as in

import .com.example.foo

to indicate a fully-qualified package name.

To me this makes perfect sense because of the convention of using reverse DNS naming, and the fact that in DNS fully-qualified domain names always end in a dot. Packages produced by the organization owning example.com. should be .com.example.*. The omission of the dot is just a shorthand.

I understand the dot is also used to mean “current directory”, but packages and directories are fundamentally different concepts. We could also say that packages are separated by . just as directories are separated by /. In that context it makes sense that since / is the root directory, . is the root package.

All we have to say is the name of the root package is empty. There are no new concepts or syntax to introduce.

I also think it’s a big plus to have a short way of referencing the root package. That would encourage developers to actually use that functionality to reduce ambiguity in their code.

10 Likes

If you want a shorthand/replacement for _root_ this makes by far the most sense to me.

I don’t mind the import .com.example.foo syntax in itself, but to me one important property of any alternate syntax for _root_ is for it to play well when used in expressions and not just imports. e.g. println(_root_.fansi.Color.Red("blah")).

I use libraries fully-qualified all the time: utest.assert(...), fansi.Str(...), scala.collection.mutable.Blah, java.lang.Double.doubleFromIntBits, etc… If I’m only using things from a package in one place within a file, I use it fully-qualified rather than importing it 1000 lines up and then using the short name (and possibly shadowing some other short name I want to keep, e.g. java.lang.Double shadowing scala.Double, or having to pick some mangled name to avoid that like JDouble)

A leading . seems like it would cause parsing problems, e.g. "hello" + .fansi.Color.Red("world") that would prevent such usage, although I may be mistaken and maybe it’s possible to change the parser around to make it work in a clear/unambiguous way?

4 Likes

I agree it has to be usable in expressions. At first glance I would think that it’s possible to correctly parse expressions with leading dots. But as you have a lot more experience in writing complete Scala parsers you are probably right.

1 Like

From the original post:

it seems to me that the main problem is that subpackages are always imported to a scope and they have higher precedence than root packages. As I’ve written before, removing subpackages from default scope will virtually remove the necessity of using _root_ leaving it for very rare cases. Am I missing something? Importing subpackages to scope by default doesn’t make the language concepts more unified either.

Additionally, IntelliJ (which is the de facto standard IDE for developing in Scala) rewrites relative imports from packages with absolute ones (during imports optimization) rendering such relative imports generally unused.

I think it would be a problem if you’re trying to pass something to a method without parens. e.g. doSomething .com.example.Foo.bar looks like it’s calling a method .com. So you would either need to add parentheses or use _root_ or some other explicit identifier to disambiguate it.

I was more concerned about imports as I usually use a local import instead of writing the full type name, but your concern about use in expressions is valid as well.

As @gmethvin said, indeed you would need to add parentheses to avoid it thinking fansi is a method on +.

I think we still achieve your objective, which I agree with, that the syntax is not special-cased for imports, and can be used anywhere. I think it’s reasonable if it needs parentheses since I don’t think the non-import use case is common. So to me, the goal is not to make it convenient to use in expressions, just (1) not to special-case imports and (2) to make it possible to use in expressions if need be.