Proposed syntax for _root_

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.

I think absolute imports should be encouraged with lightweight syntax while relative imports should come with a syntax tax. Absolute imports are inherently easier to reason about.

One proposal: make fully qualified imports the default and impose a . prefix for relative imports

import fully.qualified._ // becomes import _root_.fully.qualified
import .relative.name // becomes import relative.name

this would extend to local imports and imports on the empty package

def foo(a: Foo) = {
  import .a._
}

import .Bar._
object Bar

Nothing should change how reference resolve outside of imports.

4 Likes

@olafurpg’s proposal is the one I like the most so far.

  • We should encourage absolute imports as much as possible.
  • We can migrate code from the previous scheme to the new one with the least changes to the code (assuming relative imports are not that common).
  • . is a prefix we use everyday in our file systems, and it has a strong connotation associated to a “relative” path, which is arguably what FQNs represent (some kind of virtual filesystem of code). Using . is the right call.
2 Likes

A major issue with @olafurpg’s proposal is that there is no way to write cross-compiling relative imports that work before and after the change. No amount of scalafix can fix this: if I migrate my code, it does not compile with the old version anymore.

2 Likes

I agree this is an issue, esp. given that syntax has remained mostly unchanged 2.10 through 2.13. It should be possible to provide a compiler or build plugin to translate the new import syntax for older scala versions, the JS community has been doing this for years with Babel. Although, I admit that’s a heavy solution to support this single breaking change.

That’s much too blithe. There are a lot of us who don’t use IntelliJ, and relative imports are common in many codebases. (Especially for subpackages of the current package.) I think you’re significantly underestimating the amount of code that would be impacted by the change you’re proposing…

2 Likes

I question this assumption. Seriously, I think folks are forgetting the complexities of it. For example, opening one of my files more or less at random, I hit:

import EditFunctions._

The current file is in package querki.editing, as is the API-shared EditFunctions object, which contains all of the data structures for the API. This is the concise and obvious way for me to import those structures.

Seriously, people are asserting that this usage is rare. As somebody who uses it all the time, quite deliberately, I have to question that…

3 Likes

Unqualified imports are harder to reason about than fully qualified imports, they require awareness of what symbols are in scope at the import position. Fully qualified imports on the other hand always have the same meaning no matter their position.

My proposal was not to ban relative imports, the proposal was to demand minor additional syntax for relative imports to trigger extra attention from the reader. import .EditFunctions._ (or some equivalent syntax) is a small price to pay IMO for the increased emphasis that EditorFunctions is pulled out from the scope instead of _root_. In the ModelDesignerPage.scala source file you linked, 15 imports (or 14 imports if you exclude import rx._) out of 17 seem to be absolute.

1 Like

My understanding is that the main problem is that anything that can be an identifier can be shadowed by another definition that is in scope, so we want to use a reserved word. package seems to be the best existing keyword for _root_.

I understand the resistence to adding a new reserved word, but there is a huge set of rare unicode symbols we can choose from. Some of them could metaphorically be interpreted as meaning “root” or “start”. Here are some that I think we could use, that I’d be shocked if introducing them as reserved words would break anything.

:checkered_flag: chequered flag, indicating the start of a race.
:cherry_blossom: cherry blossom, symbolic of beginnings in Japanese culture.
:sunrise: sunrise, beginning of the day
¡ inverted exclamation mark, used for the beginning of an exclamation in Spanish
a doorway to higher wisdom
:rocket: a rocket, beginning of a journey
:red_car: a car, beginning of a journey
:bike: a bicycle, beginning of a journey
:ice_skate: ice skate, beginning of a journey

I would include ‘a’, ‘ℵ’ or ‘α’, but programmers who speak many languages wouldn’t like them.

(There’s possibly some BS here. I used a quora post and a tattoo guide for some ideas.)

Another metaphor for _root_ is that it is the entire universe, from which you add a package path to indicate a specific subset.

infinity
proper subset
𝕌 universal set
join, your program is the import and the surrounding text
marriage of the import and your program
:eye: the all-seeing eye
sun
sun
:sunny: sun
:globe_with_meridians: earth

Some of these might not be good to use due to programmers who speak mathematics.

My last metaphor for _root_ is that it brings life or meaning to the lifeless or meaningless.

flower
:evergreen_tree: tree. I particularly like this one, as the package structure is a close metaphor for a tree. There are other unicode trees to choose from.
root (see tree metaphor)
ankh, symbol of life
:heart: heart, love
:love_hotel: love motel
:couple_with_heart_woman_man: love
:rabbit2: rabbit, fertility
:chicken: the chicken to your egg (I didn’t find an egg symbol, so this answers the question: the chicken came first)

1 Like

I get that – I understand why you want this change. What I’m challenging is multiple people implying that this isn’t a common usage, so the change isn’t a big deal. That is absolutely untrue for me – I use it all over the place – and I’m concerned about the migration challenge. I suspect I’m not alone in that…

2 Likes

You are not alone in that. Especially when it comes to code that requires source compatibility with older versions.

1 Like

Also, the leading . for relative imports doesn’t solve the problem of @lihaoyi (and many others) who want to use fully qualified paths in expressions. Unless you want every non-fully qualified path to have to be prefixed with a ., which IMHO would be madness. But if you require . in imports but not in expressions, Scala’s syntax becomes less consistent.

Apologies if I’ve missed this part of the discussion, but why don’t we migrate towards making _root_ a keyword? It seems like the least painful migration of the ones I’ve seen proposed here.

3 Likes

I think because it’s ugly - at least that was @fommil’s original concern.

Unicode symbols (especially emoji) are difficult to type, and don’t render on all systems.

1 Like

I suggest lobbying with one’s IDE vendor to fold _root_ into one’s preferred emoji (may I suggest :snowman:) for the remaining matter of esthetics. See also: Wadler’s law.

5 Likes