I am not a compiler expert, but based on my understanding, while yes, supporting this would increase the complexity of the compiler, I don’t think it would be very hard.
Having said that, it probably would indeed have a tangible impact on compilation speed, which is already not great.
It would also promote what many (me included) would refer to as bad code.
So the cost seems very high, and TBH, I can’t think of any good benefit.
Thus, I doubt this trade-off would be reasonable.
It would also promote what many (me included) would refer to as bad code.
What makes you say that? Placing imports at the bottom of the file is unfamiliar of course, and so it will give programmers immediate disgust when they see it for a while, yes. But is there anything more substantive than that?
Footnotes and references are at the bottom of pages in articles. In codebases I notice my eyes and mouse-scroll immediately skip over the imports the moment I open a file, registering them as less-relevant noise. The benefit would be that the more important part of the file - the classes and functions the file is dedicated to - would be seen first, with the imports later if the reader cared to investigate that.
I can think of the counter-argument that “knowing what is being imported first will give better preparation to understand the following contents of the file”, but that seems analogous to the mainstream argument against forward-referencing method definitions, which Scala does support anyway and many programmers have settled on the opposite conclusion that seeing the reference before the definition is a better order of comprehension.
One of the big challenges in understanding code is context getting lost.
Imports are context. How do you know the context of the code you’re looking at? If imports are all together, you look wherever they are. If they must be all together, the top is the most natural place, even though it’s mostly arbitrary, for the same reason that we generally put function signatures at the top rather than the end of the function:
def foo
if p then x + y.length else x * y.length
(p: Boolean)(x: Int, y: String): Int
is perfectly complete, but you have to jump to the end anyway to interpret what you see, so why not go top-down?
Same deal with imports.
If there is a very compelling reason why they can’t go together, then yes, allow them anywhere. But if they can go together, it helps figure out context.
Okay, fair point, let me correct my wording.
Is not “bad” code, but yes unfamiliar code.
While yes, I don’t have stronger arguments for placing them in front of the file over familiarity and allowing readers to quickly check what is there.
I do have stronger arguments over adding more options to how to format code and making the compiler slower.
Note, AFAIK, Java and other languages already allowed this, so it is not really something unique to the language.
It is very common for most compiled languages AFAIK, only interpreted languages don’t allow this because of their evaluation model.
And many others prefer to do the opposite.
Others prefer to simple sort the code alphabetically.
Others prefer to follow other standards like public first, private second, but constants before all, and what not.
All that to say.
Yes, I do agree neither option has proof to be universally better or worse, and all this just boils down to personal preference.
In general, I have been finding myself defaulting to restricting personal choices in the language. It provides more uniformity, simpler and faster tooling, a unified experience, etc.
I do understand others disagree, and I do sympathize with their arguments for solo projects. But, IMHO, it is more Important to focus on team concerns.
I believe you can configure your IDE to always collapse the imports blocks by default.
Which would help you in this case.
scala> object X { def f = 42 }; object Y { def f = 27 }
// defined object X
// defined object Y
scala> def g =
| import X.*
| val a = f
| import Y.f
| val b = f
| a + b
|
def g: Int
scala> g
val res0: Int = 69
Also ordering is restricted in local contexts:
scala> def f(): Int =
| g
| val x = 27
| def g = 42
| x
|
-- [E039] Reference Error: -----------------------------------------------------
2 | g
| ^
| g is a forward reference extending over the definition of x
|
| longer explanation available when compiling with `-explain`
1 error found
Things that belong to types can be resolved “out of order”.
I think this example might supports the counter-position. I don’t think it is arbitrary. We put function signatures before implementations because the high-level idea is more important than low-level explanation. I often stop reading after the signature, it’s enough.
Likewise, we might choose to reference a function in a callsite before it’s been defined, because at a high-level, all you need to know is that this function exists and is being called here; you don’t need to know where it comes from or what it’s implementation is. Imports count as “where it comes from”, and so a secondary detail.
I personally have always felt strongly that function definitions should come before they are referenced and thought it was strange that Scala allows otherwise, but recently I feel compelled by the counter-argument I mentioned. I don’t think it’s obvious which side is correct. More experimentation needed to determine which results in easier comprehension (which we unfortunately cannot do if languages ban the choice)
I understand the advantages of standardization, but we can accomplish that with tooling. TypeScript has a tsconfig.json file that allows teams to disable or ban features and styles they dislike. It’s more than a formatter, integrates with the compiler. Of course you can argue they allow you to customize too many settings, but the basic premise of letting some things be up to the discretion of teams is nice.
It’s strange to me that Scala seems to not have anything robust and 1st-party like that; i.e. flags for mandating braces over whitespace, requiring explicit return types and so on. This could simply be another flag (if not for the more serious ordering requirements that som pointed out)
Yes this will still result in very minor inconsistency in the overall community, as some teams will choose to have unconventional rules set. But again extremely minor, If you set import_locations = "top" as a default compiler flag, 99.99% of teams will not change this. Unless enough teams start doing it and it becomes a popular movement (which would be evidence that the convention is actually good, and we are witnessing evolution)
I believe you can configure your IDE to always collapse the imports blocks by default.