Scoped Imports


#1

First off, I’m absolutely loving Kotlin - wanted a JVM language to try that wasn’t just a copy of an existing language (i.e. JRuby) and was actually understandable (cough Scala cough). My company uses IntelliJ, and since every time I try to create a new Java class I see the option to create a new Kotlin file/class, I figured I’d look into it; glad I did.

Second, not sure if this has been discussed here; tried searching, but my search-fu isn’t necessarily the best, so I’ll gladly take a link to previous threads about it.

Since Kotlin allows including multiple classes within a file, it seems like it’d be useful to include imports within a specific class at the very least; function-level scoping could arguably be useful as well, but might be a little overboard. I’m not suggesting this out of necessity on my part, but I can think of a simple use case where it might come in handy:

// SomeStuff.kt
class DataProcessor {
    import kotlin.collections.List;
    
    val data: List<Stuff> = listOf(blah);
}

class DataPresenter {
    import java.awt.List;
    
    val listView: List by listPresenterPropertyStuff()
}

Currently, there’s the “as” keyword approach (which I do like, btw):

// SomeOtherStuff.kt
import kotlin.collections.List;
import java.awt.List as AwtList;

class DataProcessor {
    val data: List<Stuff> = listOf(blah);
}

class DataPresenter {
    val listView: AwtList by listPresenterPropertyStuff()
}

Which is fine, but I can also see that method causing confusion and possible refactoring hiccups. There’s also the good ol’ fashioned fully-qualified class name, but we’re not savages. The other alternative would be putting everything into separate files; I’d imagine that if the code is big enough to where the “as” keyword leads to confusion and/or refactoring problems, it’s big enough to warrant putting into separate files. Of course, I’ve also worked on enough code to know that “breaking into multiple files” would be a task that will always get scheduled for tomorrow, right after improving code coverage of unit tests but before adding documentation.

Granted, we’ve been working around this kind of stuff in Java-land from day one, but if nothing else I imagine it might help keep generated code a little cleaner as well - no clue how the Kotlin compiler actually generates the Java code/classes behind the scenes, but it looks like it’d be easier to ensure that the generated DataProcessor class above would only import the kotlin.collections.List, while the DataPresenter class would only import the awt List.

I’m all for class-scoped imports; further scoping conditions like function- or block-level might get a little more convoluted or confusing, so I’d leave that up in the air for smarter folk to debate.

I can see the potential for ambiguity with scoping with this approach though:

// SomeOtherStuff.kt
import kotlin.collections.List;

class DataProcessor {
    val data: List<Stuff> = listOf(blah);
}

class DataReader {
    import java.util.List;
    import com.initech.legacy.ListReader;
    
    val input: List<InputStuff> = ListReader.readFromDatabase();
}

class DataPresenter {
    import java.awt.List;

    val listView: List by listPresenterPropertyStuff()
}

This might be a non-issue with smaller classes like these examples, but if the type use is removed from the class-scoped import by several lines, it might get confusing to the developer. I’d say at the very least it would warrant a compiler warning, maybe an error but that might be too strong; just something to indicate that the List they think they’re using might not be what they’re actually getting.

Thoughts? (And apologies for the long-winded post.)


#2

The JB team, and especially Andrei have always wanted strong use cases as a requirement for even considering the feature.

I’m not sure I’m seeing a strong case here. It looks like a fun thing to have, but I suspect implementing this would have many drawbacks:

  • Tools: I’m not sure IntelliJ is equiped for that. Furthermore, when you’d use a new type, where would the corresponding import go ?
  • I feel it’s counter-intuitive to have, for example, a Date object in two classes of the same file that are not the same type (one would be java.util.Date, and the other java.sql.Date). Reading this would not be pleasant.
  • Even if Kotlin allows multiple classes per file, a file is still a “logical unit of compilation”. If you have so different imports per classes, maybe they should not be on the same file?

#3

Yeah, I agree that it’s not a terribly strong necessity by any stretch and more of a “nicety” (if that).

Not too sure I agree about the counter-intuitive example though - if you’re trying to do some kind of processing that handles different Date instances differently, it would be reasonable to have multiple classes that each work on the individual Date types internally. Having those in a single file would still fall in line with a logical unit of compilation, as they’d all be fairly closely tied together, or at least related.

Can’t argue too much about the difficulty/unpleasantness of reading a file where the term “Date” could mean completely different classes in different places. That could definitely get confusing. But, there’s lots of different constructs in various languages that are great if used carefully but mind numbing if over used or used without thought to how they’d read (the ternary operator comes to mind).

I don’t think tooling would be terribly hard to implement for this; maybe default to top level for a new import, with the option to make it a scoped import (or vice-versa). Besides, even though I know Kotlin and IntelliJ are pretty tightly tied together, I don’t think the tool should dictate the language design.