New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QualifiedImports #220
QualifiedImports #220
Conversation
This proposal adds a new extension to GHC `-XQualifiedImports` which switches the default behavior of `import` from importing modules unqualified to importing module qualified. It also introduces a notion of *main type* of a module import: the main type of a module import is automatically imported unqualified. This proposal is authored by @aspiwack and @guibou. [Rendered](https://github.com/tweag/ghc-proposals/blob/qualified-import/proposals/0000-default-qualified-import.rst)
I am strongly in favour of this at a conceptual level, but I'm a touch worried about the recent proliferation of a whole set of extensions to deal with cleaning up import sections. Personally, I'd like to see some explanation of how (or not) With regards to the meat of the proposal itself, I also have a few thoughts:
import qualified OCI.Data.Graph.Node as Node
import OCI.Data.Graph.Node (Node)
Overall, I definitely understand the motivation behind this proposal and like it a lot! |
@iamrecursion thanks for the very detailed comment. @guibou is away most of the week, so in the meantime, let me try to fill the gap.
I don't expect any interaction (after all, what we are proposing is, at the end of the day, just syntax). But I'd rather wait until these proposals are accepted before making a detailed comparison.
With the current semantics of
Indeed. And, to be honest, I fail to see what an alternative could be. The spirit of this proposal is that it's fine to import a module unqualified: it just needs to be a conscious decision. |
Entirely fair. My main concern with the overlap is that they all involve syntactic changes to the import statements, and with GHC already suffering from a proliferation of language extensions, there's some not-insignificant potential for confusion.
That's a very good point. My comment was more idle speculation than a proposal for a concrete use-case. I certainly can't see a need for it at the moment.
That's absolutely what I expected! Perfect! |
I can see your motivation, but wonder if it's really worth all this just to change the default. Also I forsee that people will indeed want to nominate more than one "main type" or function. Also, with the "main type" thing, we can still get name clashes. So we need a a way to make the main type be impoted qualified, which adds more complexity. So, based on a very superficial look, I'm unconvinced that the benefits justify the costs. That said, the complexity doesn't ramify: it's a well-localised change. |
Part of my reservation i'm not sure what namespaces we ought to have in Haskell. From the dependent Haskell front, we have too many already. Rust also makes some interesting arguments about name-spacing fields, constructors, and maybe even arbitrary functions (most controversially of all) under a type. ("Inherent Given all the uncertainty in my mind, I just rather see how thinks shake out. I rather solve the more "tech debt" issues like |
Thank you for your answer @simonpj
Changing the default is the core of this proposal, we are convinced that it will lead to cleaner import behaviors. The "main type" import is just a syntax sugar on top. We can abandon the "main type" part of the proposal, but not changing the default means abandoning the proposal.
Yes, we also noticed that in the alternative section. The current state of our proposal comes with two properties:
Nominating many "main type" won't preserve these properties, especially their explicitness. If someone want to import unqualified many types or functions, she can explicitly do so using the standard way: import Data.Foo unqualified (MainType1, MainType2) I'm really in favor of explicit behavior, or principle of least surprise. "Main type" is a surprise, but with really limited impact and it follows a pattern already used in the community.
I'm convinced that the name clashes risk in this context is really limited. On the other hand, we can see this conflict as positive because they will create a dependency between the module namespace and the type / function namespace. For example, using the following import: import Data.ByteString as ByteString With our proposal, this means:
In this context, the Now, we can imagine a conflict if the I can think of three examples of such a conflict:
I cannot think of any other source of name conflict with the "main type" feature. But, anyway, if you want to disable the import of the main type, just export the module with a different name: import Data.ByteString as LegacyByteString
data ByteString = ... -- my bytestring, no conflict with LegacyByteString.ByteString |
I'm very unkeen on the idea of treating some identifiers differently in a module (the "main type" thing). It feels very much like an ad-hoc hack and bakes in a particular convention that happens to have been adopted for some (but not all) popular data structure libraries. Modifying the language to better support one particular convention doesn't seem right. If we want to do something more general here, I would like to make it possible for a module to export qualified identifiers. Then |
Bikeshedding: Why not module MyModule where
open import Prelude
open import My.Library
open import Data.Map (Map)
open import Data.Text (Text)
import Data.Map as Map
import Data.Text as T It's now obvious where unqualified names can come from, and all those fancy import formatters can manage this cleanly. By moving qualification to the end, tools (and users!) have to make it to the end of the import before they know how the import is resolved. With the above, I can scan imports and know immediately how this works. Stealing a word like |
@guibou My gut feeling is that a lot more people would be against “main” type (very confusing) than to “qualified by default” (clear enough what it does and how). |
@jhenahan, If you're not a fan of "stealing"
|
Another option which steals nothing: import Prelude as default |
First, I like the idea of this extension, insofar as making import statements default qualified. Generally I feel that Haskell's import system being unhygienic by default is a mistake, and it would be nice to flip that bit at a project level. Split out "main type" proposalThe "main type" thing should be split into a different proposal. I agree with @simonmar that it feels like a hack. If one were to attempt to solve that issue in a less ad-hoc way, I'd expect it to be something like exposing modules as records, taking inspiration from ML or even ES6. Though that's far from a trivial thing and shouldn't be coupled with an otherwise simple change.
|
I agree with @erydo about using |
Thank you for your comments. I do understand the arguments against the main type and I'll move it out of the proposal (i.e. in the Alternative section). About the About @tejon suggestion, to use Thank you, I'll update the proposal with theses changes soon (i.e. in a few days, I'm unfortunately rather busy theses days). |
Based from the proposal discussion: - The main type may be part of a separate proposal - `open` is used instead of `unqualified`.
6b33e58
to
e7fdbc7
Compare
Could we send this to the committee? Discussion seems to have converged. |
@nomeata ⬆️ |
Committee shepherd here. (firstly noting that most of the discussion thread on the proposal is irrelevant, as it refers to earlier iterations of the proposal, which has been heavily modified since.) My inclination is to reject the proposal. As per our committee process I'll put the justification below, and we can discuss. The change itself is relatively innocuous: an extension that switches the default from importing unqualified to qualified, and a new syntax However, the underlying theme here is not one of mechanism, but one of policy. The proposal argues that it would be better if Haskell authors in general switch to a style that uses qualified imports routinely. The proposed extension would both facilitate and encourage this policy. I don't think it's clear at all that using a mostly-qualified style would be better.
For better or worse, there's significant momentum behind the current style, and even if we were to decide that it would be better to use a different style, we'll be stuck in a world with both styles essentially indefinitely - I don't want to think how many 3-release cycles it would take to change the |
Were this proposal to be adopted, I feel strongly that it should affect only the default. Regardless of the setting of the default, you should be able to specify what you want:
At that point you can write code that will work regardless of the default, simply by being explicit. I like that. And then, is it worth having a flag to control the default, when you don't explicitly say "qualified" or "unqualified"? Maybe so. This feels very much in the same category as the post-postive qualified idea: it makes a difference to some, and other things being equal that's clearly a good thing. Finally, what is the default-default; that is, the default when you specify no language extension. Here it's harder to argue for a change, because it'd invalidate all existing code. And the proposal does not make such a case; absent an extension flag, the existing behaviour remains unchanged. |
Indeed, that is the case. The proposal chooses
It's similar to the QualifedPost proposal in a literal sense - it's just a syntactic change - but it feels different, because this proprosal's underlying motivation is the suggestion that we should change how we design and use APIs. And hence, by accepting this proposal into GHC we would be facilitating (and arguably endorsing) that policy change. So it seems like the right time to make an editorial decision on the policy, not just the mechanism. If we decide the policy is bad, should we nevertheless accept the mechanism, for the benefit of those who feel differently? I don't think so - the existence of the mechanism itself would lead to some fragmentation of the ecosystem into those who want to use this style and those who don't, and that's arguably not a good thing. If it were clear that the community really wanted to move in this direction then I think it would be different, but I'm not seeing that level of support. |
I don't think so. The proposal explicitly says that
I dont't think so. It is not changing anything unless you switch on the flag. And if you switch on the flag, it just changes what happens when you say It looks very opt-in to me, rather than changing how we design and use APIs. |
Hm. That's a good point. I'm now right in the middle. Maybe leaning ever so slightly in favor. |
Taking that a step farther, I don't think this module affects reading a module at all except for the (local, obvious) module Foo where
import Data.Text
import Data.ByteString as B
foo :: Data.Text.Text -> B.ByteString
foo = B.encodeUtf8 This module is legal with or without the proposed extension. It might seem like weird style, but everything is legal and no meanings are changed. Until and unless you want to edit it, there is exactly zero cognitive load here. Adding In fact, if we switch from a pseudo-keyword to an inline |
Update on the status and points made so far: The proposal is an opt-in extension. This is true of course - but it's also true of all extension proposals. We should consider whether adding this extension is a net benefit. My personal view is that we should accept extensions if we think they have a reasonable chance of making it into a future language standard. This extension is quite speculative - the authors argue in favour of a different style, which this extension would facilitate, and in order to find out whether the style catches on, we need to add the extension and see what happens. If the style doesn't catch on, then having the extension at all would be a net negative - extra complexity, documentation, and more chances for people to get confused. Do we believe the style will catch on? I'm sceptical - in addition to the points I raised earlier, based on working with a lot of Haskell programmers over the years and seeing what styles people prefer, generally it seems we prefer to find a good balance between qualifying things (which optimises for first-time reading of code in an unfamiliar codebase) and not qualifying (which optimises for working with the code in a familiar codebase, because the code is less cluttered). Also I'm just not seeing a whole lot of support from the community for this proposal. This matches what I've seen elsewhere - there are a few people who strongly believe in qualifying as much as possible, whereas the majority are happy with the status quo. I think the minority are already well supported by explicit The qualified-by-default style optimises for reading over writing. I think this could be argued either way - I find lots of qualified names make the code look very cluttered, which makes code harder to read by people who are familiar with the local "vocabulary". Yes it optimises for first-time reading of code in an unfamiliar codebase, but I'm not sure that's the case we want to optimise for. |
That is true enough. But it's worth remembering that Haskell programmers are a diverse bunch. It's entirely possible for a style to catch on with one subgroup, who really appreciate it, and not with another. And Haskell has lots of "two ways to do things": let vs where, case vs pattern matching definitions, etc. I'm not making a strong argument for this particular extension; just saying that for me, when it comes to low-complexity syntactic variations, it's enough that a substantial constituency is strongly in favour. I would not want an extension advocated by a single person; but I would be influenced by knowing that there were lots of production users who want it. Our difficulty is that a committee discussion is good for eliciting technical clarity, but not well suited to discovering if such a constituency exists among users. I wonder if it would be worth thinking about such a mechanism? I'm not quite sure what. At the moment all we have is "how many people made supportive noises in the discussion thread", something that is complicated by the fact that (in general) proposals often morph a bit during discussion. Syntax is always hard. |
Of course, there's no requirement to continue following a precedent if one believes it was misguided, but this proposal feels most similar to |
I'm not particularly convinced by the optimized for reading-vs-writing argument either, for precisely @simonmar's reason. Excessive qualification makes code difficult to read, and the question of "where is this function defined" seems much better answered by good tooling in my opinion. But this is not the primary motivation of the proposal. The proposal wants to address the issue of maintaining import/hiding lists, which certainly can become a pain. One alternative not mentioned by the proposal, which I have gravitated to, is to discourage the use of import/hiding lists. Instead, either import the entire module unqualified, or qualify it. This is not a rigid rule for me, but it seems to work quite well as a rule of thumb, and most of my imports end up unqualified. To do this successfully though, you need to know which modules really need to be imported qualified. Some are well-known to any seasoned Haskeller (text, bytestring, the myriad containers, etc.). For other libraries, one thing I've noticed authors doing is specifying in the haddocks that the module is intended to be imported qualified, which is very helpful. So I think that the underlying issue this proposal wants to address could be successfully tackled by the community without changes to the language. That being said, I find it hard to argue against adding the option to change the default, as it's a small, stylistic change with a small maintenance burden for GHC. I've always appreciated Haskell's large source language that accommodates differing styles, and this very much feels like a decision that different teams could reasonably disagree on. |
Out of curiosity I looked at a couple of large production Haskell projects at work (over 100k import lines in total) and found
Caveats apply of course - there is a local style guide that encourages a certain style, although it doesn't say anything about qualified imports. Import lists are an unsustainable drag in practice, so we mostly don't use them. In GHC we came to the same conclusion several years ago: originally GHC used import lists everywhere, but it became just too painful, so we removed most of them. Life has been much easier since. Developers generally seem to prefer to use unqualifed imports until they encounter a name clash, and then they use either For GHC the figures are slightly different:
I think this reflects a preference to use import lists over qualification to resolve name clashes, and perhaps some inertia from the time when we used import lists everywhere. |
There's a quantitative reason (versus qualitative/stylistic "easier to read") for why explicit or qualified imports are valuable to a reader, and could theoretically be helpful to tooling, and so are worth encouraging. Basically, by being able to determine locally and syntactically which import introduced a symbol, you reduce the complexity of symbol search down from "search the entire module graph" to "follow the linear path of imports until you get to the definition". Currently, we take for granted that tools crawl the entire module dependency tree before being able to provide any symbol information. Under this view, the difference between qualified and unqualified symbol use seems merely aesthetic. However, as a human, I've very rarely read the entire import tree. When diving into an unfamiliar project, explicit or qualified imports help me so much, because I can see exactly where to look for the symbol definition. This is especially helpful when there are external libraries: Tooling can also benefit when explicit/qualified imports are used by prioritizing which modules to crawl or build when first opening a file and receiving a symbol request, before finishing loading the entire project. For example, if you open a file with a dozen imports, and ask what As an aside, explicit exports have a similar benefit, from the other direction of the search: If you can look at a module syntactically and see that it does not export a particular symbol, then you don't have to consider its imports when looking for that symbol. (Or, at least limit the search to re-exported modules). Helpful both as a reader, and potentially to tooling in the middle of loading module information. It's true that there's an overhead to being explicit; it's way easier to just throw up your hands and "import everything from everything and figure out what I mean when you compile it". But there's also more than pedagogical value alone to qualification or explicit imports. It isn't an all-or-nothing tradeoff, either—it applies to each symbol used in that way. Given that reading happens under many conditions, and writing usually happens in the context of a functioning and installed development environment, my opinion is the balance of value might tend to lean toward "optimize for reading". (I also think that as tooling improves, those import lists might be better automatically managed, so that developers can code as though using open imports, but have a beautifier unfold or qualify them. Best of both worlds; developers shouldn't have to deal too much with those kinds of style choices). |
Massive +1 to this; it's the perfect distillation of how I understand "optimize for reading" in this context, but there are likely other interpretations and I'm glad it occurred to @erydo to spell it out so clearly! |
Ok, let's see where we are with this proposal. I have personally argued against it, for reasons explained in more detail above, but which I'll summarise briefly:
I think to accept this proposal we would need some evidence that there is a substantial cohort of users who would want it, or a reasonable chance that it would catch on. @simonpj said above:
I gave some data above that suggests, at least in some large production codebases, we don't see a tendency towards more qualification. The other data point is the reactions on the proposal (currently 5+ 6-). Do we have any other data here? |
I'd like to contribute a data point. I used to work on a large Haskell codebase (100k+ lines of code) where the style guide required to use either qualified imports or explicit import lists. People typically used explicit import lists rather than qualified imports. This supports @simonmar point that
In practice, it ended up a very painful experience, as almost every merge/rebase ended up with numerous conflicts in the import section. It was such a major time sink that I felt like an import section engineer rather than a software engineer. My conclusion is that explicit import lists are not a viable code style, at least as long as there is no tooling with custom merge strategies for import sections. A much better workflow is to use unique identifiers project-wide, prefer unqualified imports, and set up jump-to-definition (fast-tags works rather nicely). This said, the proposal argues for use of qualified imports specifically, not explicit import lists. I've noticed that the two are mixed together in the discussion! Let's not mix them up: I don't think qualified imports would entail the same issues that I experienced with explicit import lists. |
Dear author, @guibou, you have not responded to the recent conversation. How are you feeling about the proposal now? There may be changes you want to make. For example, I strongly recommend that I note that we accepted a proposal in very similar territory, simply moving where the As to support, in this thread apart from @guibou himself, I'm not sure I see anyone else in strong support. (@erydo and @tejon argue for "optimise for reading" but I'm uncertain whether or not they are arguing for this proposal.) The disucssion is a bit confused by the fact that the proposal was originally more elaborate, with a "main type", but was then simplified. We are in committee-review mode now, which makes it harder to gather such feedback. I'm firmly on the fence. If it were just me, I wouldn't do it. But if a significant (even if small) constituency wanted it, I'd be happy to accommodate it. @guibou do you have any evidence that there are others who want this, or is it just a good idea you had and thought was worth discussion? |
I think there's actually a big difference between the two proposals. |
For the records: it's very much not about import lists. Rather, it's about avoiding import lists (in part because of the phenomenon you describe). |
@simonpj — To be explicit, my argument is in favor of the proposal. At least, the spirit of it. I believe it encourages a "better" default semantics for imports. My only remaining nit was the minor syntactic comment which might have been overlooked or just not made it into the text:
Regarding @simonmar and others' concerns about confusion (which are worth considering), my thinking is:
|
@tejon was kind enough to respond privately (because it's under committee review) to me, with permission to share: "Apologies for not being explicit: I support this proposal and would immediately argue to enable it globally in my company's codebase (and I don't anticipate resistance on that). It is likely that the majority of our imports would still be "I'm also very much in harmony with everyone decrying the pains of maintaining import lists! Qualified imports have a completely different profile there, which is why my company's style has evolved to favor them in general. For a data point: roughly 25% of our current imports are qualified; only 10% have an import list, mostly for operators." That's useful information, thanks. I think that if any others would like to speak up briefly in support, we'd be glad to hear from you, on-thread. I don't want to start a new for-and-against debate, just gather info on whether there are others thinking "oh yes, if this flag existed I'd use it a lot -- even across my company". |
I do agree, it should remain legal. We initially wanted to forbid However, if the @tejon gives a really interesting comment (#220 (comment)). He observed that "qualified import style" is actually working without the qualified by default. Turning on the extension just makes this programming style more robust.
I have no evidences unfortunately. All started from a discussion between me and @aspiwack where we realized that we were agreeing on our interest for qualified by default. I have an observation however. The qualified by default style is Python's default. However one may argue that qualified by default is a style which is not surprising for python developer coming to Haskell and I've heard a few people coming from python and surprised by the import semantic in Haskell. @erydo commented about the syntax:
I totally agree with that and I apologize if the current proposal text is unclear about that matter. As a personal experience, I'm working on a 100K+ lines of Haskell codebase. 8% of it are import lines (probably even more, I just counted Considering the current state of the discussion, I'm motivated to change the proposal as such:
Thank you. |
So is Ocaml's.
I would be perfectly ok if |
True, however in Python it works in conjunction with the convention of terse module names and less use of a hierarchy. So in Haskell to use this style we'd be using And let's not forget that you can already do this today! If there are people who believe strongly in this style of importing, there should be example of it in the wild, no? Those who feel strongly wouldn't be put off by the need to write one extra keyword per import, I would think. |
Not to let this linger too long, let's try to arrive at a decision here. I'll propose that we reject the proposal. It's clear that there's a section of the community that feels strongly that imported identifiers should be qualified by default. I don't think that's an unreasonable position - indeed as has been pointed out, other languages successfully take this approach. I'd like to thank the authors for the proposal and the enlightening discussion it has generated. Extensions that only a subset of the community would be happy to enable should incur a much higher level of scrutiny, because this essentially introduces a fork in the GHC language. Some modules would enable the extension and some wouldn't, so Haskell programmers have to be aware of both variants of the language, increasing complexity and steepening the learning curve. We might adopt an extension of this kind if it was clear that we (as a community) wanted to migrate in a particular direction over time, but in the case of this proposal I don't think that's the case. We have a section of the community who feel strongly, but no indication that this section is large or growing. Any more thoughts before we close the proposal? |
As I say above, I don't feel strongly either way, I'm content with Simon's proposal to reject. The only issue is one of defaults, not of exrpessiveness. I see no global consensus that that qualified import is a better default. And locally, one can always establish a coding convention that you should always import |
While I like the proposal and feel that it's a good direction to move that way, I agree with the @simonmar's reasoning and support rejection. |
I do understand your motivation for the rejection. I thank you all for the time you took to review and discuss this proposal. Hopefully I'll have more success in my future proposals. |
@guibou, please don't regard this as "failure" and acceptance as "success". Everything in language design is a balance, and a judgement call. Not much is right or wrong, black or white. We make progress by trying things, sometimes drawing back, and trying again. (e.g. I'm on my fourth attempt at finding a decent way to add impredicative polymorphism to Haskell.) Every exploration means that we understand the space a bit better. Thank you for contributing to that exploration. |
This proposal adds a new extension to GHC
-XQualifiedImports
which switches the default behavior ofimport
from importing modules unqualified to importing module qualified. It also introduces a notion of main type of a module import: the main type of a module import is automatically imported unqualified.This proposal is authored by @aspiwack and @guibou.
Rendered