-
Notifications
You must be signed in to change notification settings - Fork 25
Formalize ".Internal" module convention #46
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
Conversation
Here we formalize the convention of using `.Internal` modules to provide potentially unstable interfaces. Addresses haskell#8.
I don't think this needs change in PVP. The breaking change is already SHOULD (not MUST). People can say in documentation that modules are not part of PVP. However, if you want to formalize that, you'll need to add an additional point like:
e.g. 6b. Client imports Remember, the PVP is policy for both, library producers ("authors") and library consumers ("users"). I'd also like https://wiki.haskell.org/Import_modules_properly to be folded into PVP spec. That part is overlooked, and causes some pain. (TL;DR don't use unqualified imports if you want to use major bounds). EDIT: Yes, I'm fully aware that would make using I'd also argue that e.g. Minor comment: the |
Interesting. Can you say which clause in the current PVP you are referring to? I see "SHOULD" only in clauses 7 and 8. An omission of the current PVP is that it makes no distinction between exposed and un-exposed modules of a package. I think, for the avoidance of doubt, that it would be helpful to say explicitly that clauses 1 and 2 (breaking and non-breaking changes) apply only to exposed modules. Changes to un-exposed modules come under claues 3 (other change). That is doubtless obvious to many readers, but (I can certify) not to all. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're formalizing a convention that has been used by people in the wild. But that doesn't mean this is the best way to signal "this module is exempt from PVP".
Have we discussed other options? I think this needs to be discussed.
Further, this definitely is a breaking PVP change in my opinion. Tools assuming current PVP wording will not work correctly with this change and need adjustment.
I feel this is too much of a big change to apply ad-hoc without larger community consensus.
@@ -48,7 +48,10 @@ ordering of the components. For example, 2.0.1 \> 1.3.2, and 2.0.1.0 \> 2.0.1. | |||
version number. When a package is updated, the following rules govern | |||
how the version number must change relative to the previous version: | |||
|
|||
1. *Breaking change*. If any entity was removed, or the types of any entities | |||
1. *Internal modules*. If a change only touches entities defined in modules | |||
whose names include the string `.Internal` then *A.B* **MAY** remain unchanged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel this is somewhat ambiguous.
- what about
Foo.Internal.Bar
? - what about
Foo.InternalBar
?
Both include the string .Internal
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, perhaps appealing to the module hierarchy instead of modules names as strings would be more appropriate here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if a package is called Internal
? Do we just say "don't do that then", or should it be a non-initial component?
The way I see it, we would want both AwesomeLibrary.Datatypes.PriorityQueue.Internal
and GHC.Internal.ByteMunging
to be considered internals, but Internal.Datatypes.List
to not be.
Perhaps it's OK to just lose that namespace, though - it would be pretty confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's exactly what's problematic with magic names.
PVP already implicitly makes a connection to cabal file syntax. So what if we we add support for "internal" modules to cabal first and then adjust PVP to reference it.
E.g.
exposed-modules
(exposed and PVP compliant)other-modules
(not exposed)internal-modules
(exposed, but exempt from PVP)
This also allows easy static analysis and doesn't depend on module names.
It could be argued PVP is more general than haskell, but reading the spec carefully, that doesn't seem to be the case anyway.
The spec could also leave this open and intentionally vague and just point out that breaking changes can be exempted by a language's metadata format, specifying those modules in an appropriate format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what if we we add support for "internal" modules to cabal first and then adjust PVP to reference it.
This seems to me to be a great thing to have done years ago. But today, we happen to have a spec that says to do one thing, and a lot of people who actually do something not quite what the spec says. Propagating a new file format to do this means convincing everyone to abandon their current practices, and move to something else, as well as waiting for the new versions of the various tools to propagate and spread.
Why not formalize what people actually do, which seems to work reasonably well in practice today?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not formalize what people actually do, which seems to work reasonably well in practice today?
Because they don't. People treat .Internal
modules of bytestring
or text
as stable and under PVP. Please address that specifically. (And servant
fwiw).
EDIT: In particular I have never seen a client using Internal
modules specifying minor version bounds. They all use wide major bounds e.g. bytestring >=0.10.3.0 && <0.12
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not formalize what people actually do, which seems to work reasonably well in practice today?
Because that's how specs generally deteriorate and simply become documentation of current (good and bad) practices.
I'd rather work towards making the spec more general and leave space for languages and tooling to decide on non-critical things.
I also don't see any particular time pressure here. Are we in a rush?
I don't understand; the "breaking change" paragraph explicitly says that breaking changes (e.g. entity removals, orphan additions, or type changes) MUST be result in a version increment.
Perhaps not. However, given that this widely used convention is currently constitutes a violation of the PVP (see above), I think it would be reasonable to both formalize the status quo and simultaneously discuss alternatives.
Of course; I fully expect there to be discussion on this matter. This MR is merely intended to start the conversation. |
What tools are you thinking of in particular? As far as I can tell, they would already be broken with a significant fraction of code found on Hackage today. |
Any PVP compliance tool. Technically, they would be correct to not treat 'Internal' modules specially. Otherwise, why are we raising this PR? So the bug lies within the hackage packages, from the PVP compliance tool POV. I'm not saying this is a problem, but will require a new spec version. |
I remembered that 1. clause has SHOULD, the MUST there is IMO a tad bit too strong. |
Looks to me like it has always been there. What would be the rationale to make it SHOULD? Wouldn't that render the guarantees of upper bounds useless? |
One example is packages like I also remember discussing some changes which would required major bump, but it was considered better to do a minor bump. Unfortunately I don't remember where (IIRC some of ekmett's packages, not so long ago). And also After all
But I understand that people make mistakes, and/or not always fully understand full implications so using MUST i.e. an absolute requirement is "safer". |
"Unstable" does not necessarily mean "excluded from PVP". It's helpful to mark parts of the public API as unstable or unsafe (= potentially violating internal invariants), just bump a major version each time you change them.
|
@Bodigrim how would that work for base or other boot libraries? We have to add new boot libraries? Do we have a process for that? |
There is no process, just a good will of GHC team. Cf. recent split of FWIW |
I support this change to PVP and I'd like to see it being accepted! 🙏🏻 The There exist a real problem in the design of public APIs:
So, there should be some way to satisfy both. I see the
Addressing the specific points and questions in this PR:
What are the other options? So far, I've seen only two suggestions in this thread:
I propose to create the "Definitions" section, and put a definition of an internal module in there. Something like this:
"Avoiding the formalisation of existing practices" doesn't sound to me like a universal rule to follow. Every case should be considered separately. What if the practice is really good? Are we going to avoid it just because we should formalise something different? As I mentioned previously, I see a real problem and I don't see a better alternative. Getting more feedback could help to get more options 😌 |
I see one problem with the Some packages provide Internal modules, but follow PVP regardless for those. This might become a problem if we want to make GHC aware of it and emit warnings. One could argue they can silence it with Why does this matter? Because as @Bodigrim noticed: internal doesn't equal unstable (or "exempt from PVP"). With the cabal file stanza I see the following problem: It's now a cabal thing. That means GHC can't check it ad-hoc and that will also affect e.g. |
I want to repeat myself (and @Bodigrim and @hasufell). Internal modules doesn't mean Unstable. They are just Internal, something you shouldn't need but it's there in case you need them. Don't make I want be able to depend on |
As another comment. The patch doesn't discuss any changes in client specifications. It should, how users of libraries should specify upper bounds if they import |
Under the status quo there are three options for a package maintainer:
This issue suggests adding
If you don't want the PVP to adopt the proposal here, which of 1-3 would you say is acceptable? |
I want to note an overall tension regarding modularity and versioning that I fell it would be good to have some way to remedy. I feel this tension is sufficiently pervasive and underlies enough problems that it is worth giving a name to: The Versioned Modularity Problem. If I am using a package and it does not expose some constructor, but I need to make use of that constructor, then I am very frustrated and wish the author had expose that constructor. Therefore, packages should always expose their internals! However, if I am using a package and it does expose some constructor, but only "just in case", and now that constructor changes in a way that no normal end user cares about, that is still a breaking change and I have to change my upper bounds for no good reason, as do all other users of that package. Therefore, packages should avoid exposing their internals! The versioned modularity problem is a puzzle: how can one structure packages so that violations of intended encapsulation do not disrupt the monotonicity properties of package versioning. I suggest that the idea of "Internal as PVP exempt" is an attempt to solve this problem, but one which people have provided some pretty good arguments against. |
Yes, I think in an ideal world individual identifiers would be versioned, not packages, but we are far from that world. |
There are fourth and fifth option. Both in use today. Fourth: Specify stability per module basis. This is flexible as it doesn't make any Fifth: Have a separate package (e.g. I have thinking about the latter in context of So there are ways to work around "Versioned Modularity Problem". I agree they are a bit of a burden for a developers of the libraries, but no additional rules for the consumers (which hopefully are more). 1 We may disagree whether that a good development/release practice, but it's there if someone needs it. For example churning |
I'm not sure what this means. For one of the things it might mean, doesn't it have the same problem as 2?
I find this quite plausible. |
No. If a maintainer exempt a module from versioning, marking it as unstable, than no need to churn major versions. Most package consumers won't care/notice. That's a practice which OP tries to formalise, but OP wants to "hardcodes" all |
Ah, I see. You are suggesting that the issue should be a matter of the package maintainer providing some documentation of the PVP exception, rather than the exception applying to all modules with |
I think this is the best solution. Also see http://nikita-volkov.github.io/internal-convention-is-a-mistake/, I'm pretty much in agreement with @nikita-volkov there. |
I'm sympathetic to the As such, I'm -1 on formalizing the @Bodigrim do we exercise formal voting on PVP matters similar to base (including hackage trustees of course)? This ticket has been open for a while and it's correlated to the base split discussion. I suggest to exercise voting in a week or two. |
I find the motivation for this ticket rather week. As other have outlines, we have the facilities to do this today. ( |
@Bodigrim Thanks for the link to the I see only two downsides to it:
Thus being said, I find the option suggested by @phadej the most appealing:
Again, I'm up for any approach that solves the problem with the least required effort. However, the Haddock documentation doesn't list possible values for the From Haddock docs:
Great 👍🏻 If I were to move the conversation towards the "documentation route", I'd suggest introducing the following three values:
|
If you are going to change internals, you'd need to release a new version of package(s) anyway. I don't see how there is truly additional work? And if you depend on the internals of other packages, you implicitly agree that things change, and having these internals change with a policy is a lot better than changing without any policy (or an ad-hoc one). In |
In my experience, there're much more problems with hidden internals when people can't access functions they want and much fewer problems with unstable internals when it's said explicitly that these parts of the codebase are for internal usage only. I'm trying to solve a pragmatic problem here: let people use the internals of my lib if they really want with as little work as possible from my side. |
I'd say this is where we disagree in our philosophies. I don't think that allowing to tinker with internals needs to require as little work as possible from the authors of a library. I don't think it should require a lot of work, but some work should be acceptable. There is always an option to not expose internals (no work), or make the internals stable (potential version churn), or expose needed functionality in some other stable way (some thinking and implementation work). My recent experience exporting internals in a module marked as "may change at any moment, use at your own risk" doesn't really work: haskell/aeson@6c2050f#r102022391 So even you say this module may change at any moment, someone will complain anyway, even if you warned them beforehand. Note: I'm being kind not just removing that module / its contents, but waiting until there is other needs to do major bump. And warning people that it will soon go. Nothing is ever enough. |
Sorry, but this is not a good basis on which to develop a specification on. The result of being lax and allowing ad-hoc escape hatches is this: chaos. If this requires maintainers to do a bit of additional work, so be it. If they don't want to do it, they can stay out of hackage and maintain their library on Github. |
Yes, a formal CLC vote + approval from Hackage Admins would be due. I am reluctant to trigger a vote, because AFAIU this is not the intention of the proposer just yet, see the closing line of #46 (comment). I assume when the time comes the proposer will write up a more extensive proposal with justification and discussion, otherwise it is very thin.
Besides sending a patch or making a fork, as a nuclear option users can always define a copy of your data type and |
Some thoughts that arose while reading this:
I suppose the themes are mistaking social politeness with robotic certainties, and shoehorning needs across different dimensions into one solution. Undecided on the proposal as a whole, still marinating. |
My loose thoughts:
|
The PVP may provide some general guidelines, but it is ultimately the responsibility of the package authors to define the stability of their packages, i.e., what is or is not a breaking change for the purposes of incrementing version numbers. Everyone has a different idea of the behaviors they care about. A package versioning policy is inherently a fuzzy specification because of the subjectivity in what constitutes a "breaking change". The PVP lists some concrete examples of breaking and non-breaking changes, but it's far from exhaustive. Is performance observable? Is the order in which a program reads files observable? Would you care if GHC compiles modules in a different order? I'm not sure I would even treat the examples that are provided by the PVP as absolutes. For a type-level programming library like first-class-families, most of the definitions are This fuzzy interpretation does make it more difficult for tooling to implement the PVP (whatever that means), but (1) there is no widely used such tool to my knowledge, (2) if the tool aims to usable in practice, say to be somehow deployed on Hackage, it will need a way of dealing with exceptions to the rule because (i) breaking changes at the level of function behavior is undecidable if you even have a formal definition of it, (ii) a lot of (most?) existing packages on Hackage do not follow the PVP closely (because the PVP is underspecified as argued above), (iii) mistakes happen. That could just mean the tool implements a simplistic rule regardless, and people will know to ignore warnings on modules that are I think the PVP is underspecified and the |
That's exactly the point of a spec. PVP says what must constitute a major version bump and leaves the option to the maintainer whether to be more aggressive (e.g. behavior changes, which are also discussed here: #10). This is not "fuzzy". This is exactly how most specifications work (including the Haskell report). They're not exhaustive and are so deliberately.
This can't be answered easily, so PVP refrains from commenting on it. It's too compiler dependent and underspecified.
That is not necessarily part of the programming interface. End-user programs are a completely different problem. However, again, this is discussed in #10
Well, I'm not sure PVP ought to be on top of all the latest type level stuff, which are all GHC extensions and not in any proper language spec/standard.
This is a feature. To a degree. |
I could make a type level programming library with only classes and instances (no type families), and my point would still apply. Any change to those would be a breaking change if the examples in the PVP are to be taken literally, so a "type-level" translation of base would have to be versioned differently from base. |
Thanks for clarifying this. This changed my mind about how to interpret the PVP. In that case I'm in favour of keeping the PVP minimal (and switching my practice from My point about versioning type-level programming still stands but that's for another discussion. |
Can we come to a conclusion here? Is this even still relevant given the accepted tech-proposal wrt base split? |
The accepted base-split proposal doesn't address internal modules in general. However, I think it reflects pretty clear consensus from leading community bodies (clc, ghchq, twg) that the internal convention is to be avoided and instead split packages with proper pvp are to be preferred. I think the current proposal should be closed on that basis. However, a proposal would be welcome to amend the faq to encourage the spit-module approach be adopted as a convention. Note that this is only changing the faq, since it is inline with the pvp as it already exists. |
|
Here we formalize the convention of using
.Internal
modules to provide potentially unstable interfaces.Addresses #8.