Proposed syntax for _root_

I think this is the best suggestion I have seen.

I’m pretty negative on breaking changes at this stage on something as basic as this.

The nested grouping is a natural extension all by itself, and I’d want it even if I didn’t care about absolute imports. For instance, at my company:

import com.stripe.{
  foo.bar
  baz.bang
  thisAndThat
  andTheNext
}

5 Likes

There is one alternative that I don’t think has been raised yet: parameterize import. If it looked like a method with a single implicit parameter, then you could

import           my.thing._
import(relative) bippy.quux
import(absolute) quux.bar.foo

The disadvantage of this is that import is not a method, relative and absolute should not actually be in the symbol table in scope, and even if it was a method you can’t pass arguments after a method name without parens.

The advantage, however, is that it’s extremely obvious what is going on because ( and ) are not valid package names and visually separate the behavioral “parameter” from the imports, and because you can choose names that are actually meaningful in context. Furthermore, it’s possible to define more than two, if you want; so far we have discussed three behaviors (relative, absolute, and preferAbsolute). But there are others. For instance, you might want to import only symbols that aren’t already in the table, e.g. import(ifMissing) java.util._ to avoid shadowing scala.collection.immutable.List.

@Ichoran The only issue with this proposal would be that it cannot be used in an expression position. Otherwise, I like the idea of having at least import[relative] and import[absolute].

A workaround in expression position would be

val foo = bar({ import[absolute] my.shadowed.thing; thing })

But that’s pretty unsatisfying. Then again, that kind of shadowing is pretty unsatisfying to begin with.

Using x.y in an expression is not just about shadowing. It’s for when you want to use x.y without importing everything from x. As @nafg explained, that’s how you access fields and methods.

A fully qualified access like .my.foo.bar avoids worrying whether my is shadowed or not, so you can use it even when nothing is shadowed.

FWIW, a starting dot reminds me of ::std::whatever in C++ — the initial :: also means “from the root”.

Let me spell it out for stronger effect:

In any language design, the total time spent discussing
a feature in this list is proportional to two raised to
the power of its position.
0. Semantics
1. Syntax
2. Lexical syntax
3. Lexical syntax of comments

EDIT: I also enjoy relative imports, also from subpackages. Simplified example from actual code, off the top of my head:

package ilc
package feature.booleans

import feature.sums //relative to ilc
//... define booleans in terms of sums ...
1 Like

Let’s create a Scala Improvement Proposal (SIP) for a tree imports.

Do you allow multiple levels?

For example:

6 Likes

While we’re at it, why not generalize the syntax to expressions?

// Write:
System.{ out.println(lineSeparator) }
// For:
System.out.println(System.lineSeparator)

OCaml has a similar syntax:

> let mapmap f = List.(map (fun xs -> map f xs)) ;;
val mapmap : ('a -> 'b) -> 'a list list -> 'b list list = <fun>

// for:

> let mapmap f = List.map (fun xs -> List.map f xs) ;;
val mapmap : ('a -> 'b) -> 'a list list -> 'b list list = <fun>

Pascal also has something similar (the with keyword).

4 Likes

Not being able to compose imports is a right pain. So often I want a set of imports available in every file of a package.

3 Likes

Allowing multiple levels seems both sensible and natural.

Imports are really flexible. For example:


case class Add(a: Int, b: Int)

object Eval {
  def apply(op: Add): Int = {
    import opp._
    a + b
  }
}

I’m aware of that (same thing with OCaml BTW). Still, I would prefer to write:

def apply(op: Add): Int = opp.{a + b}

That’s more fluent and more natural IMHO (in addition to being shorter). And it seems like a logical extension to the import syntax you propose.

2 Likes

It looks rather confusing to me. And I guess this would only work for cases where literally everything you need is a member of the same path. For instance:

object foo {
  val a = 1
  val b = 2
  val c = 3
}

def bar(f: (Int, Int) => Int) = foo.{ f(a, b) + c }

I think the only consistent way to desugar this would be foo.f(foo.a, foo.b) + foo.c.

How often would you actually be able to use that?

No, it’s just syntax sugar for the full import form, quite simply. Something the compiler can do while parsing into the AST:

def bar(f: (Int, Int) => Int) = foo.{ f(a, b) + c }
// i.e.,
def bar(f: (Int, Int) => Int) = { import foo._;  f(a, b) + c }

Notice how OCaml does it in my example. The f that is referred to is not from the List module.

2 Likes

Then I would just prefer the full form with the import. In foo.{ f(a, b) + c } it looks like you are prepending foo. to every term. That’s also how the import foo.{ ... } syntax works.

5 Likes

Fair enough. It’s true that it’s not exactly similar in semantics.

what have I done? :scream:

9 Likes

This discussion inspired me to add ("_root_." . 46) to prettify-symbols-alist when in scala-mode and my use case (which sound like yours?) is pretty much fixed.

1 Like

very nice. I will copy you and declare this solved :smile:

For those of us who don’t use emacs, however…

you know what to do :wink: