Proposed syntax for _root_

:+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

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