Skip to content
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

Fortified language editions #636

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

goldfirere
Copy link
Contributor

@goldfirere goldfirere commented Feb 5, 2024

This extracts out from #628 the main payload of fortified language editions, including concrete descriptions of some, including a Stable2024 that should be able to be supported, unchanged, until 2030.

If we can move quickly enough, this could supersede GHC2024. But it needn't do so -- this would still work after GHC2024 is released.

Rendered (GitHub's renderer seems to be broken. 🤷‍♂️ Thanks to @shlevy for finding a working render.)

@tomjaguarpaw
Copy link
Contributor

when I follow that link today, it's not rendered

Maybe the same issue as discussed at https://mail.haskell.org/pipermail/ghc-devs/2024-January/021490.html.

@shlevy
Copy link

shlevy commented Feb 5, 2024

You can see it in the readthedocs build at least https://ghc-proposals--636.org.readthedocs.build/en/636/proposals/0000-language-editions.html

@simonpj
Copy link
Contributor

simonpj commented Feb 6, 2024

Thanks for writing this up. I have many questions. Many of them may
best be answered by improving the specification, and pointing to the fix.


The "Proposed change specification" does not define a language edition. I think you intend us to inherit an ituition from Section 1 Motivation, but the proposed change spec should stand on its own.

We also have a brand new concept of a "bundle". Apparently a bundle can control extensions and warnings. So it is kind-of-like a language edition. But not really, I think. For example, I think (but I am not sure, it's not specified) that a language edition can change what is in -Wdefault, but a bundle cannot. Can you be precise about what a language eition is, and what a bundle is?


Let me note that

  • Today -XImpredicativeTypes means one thing. Under this proposal,
    just saying -XImpredicativeTypes means nothing. I would have to
    say "-XImpredicativeTypes as implemented in Stable2024".

    I regard that as a significant disadvantage. Not a show-stopper, perhaps, but a significant disadvantage.

  • A consequence: if I change the language edition in my .cabal file,
    that potentially changes the meaning of every {-# LANGUAGE #-}
    extension in every module of my project. That is... unsettling. It
    is hard to know what has changed, or if it matters. Upgrading a
    project to a new language edition could be tricky. In contrast, if
    language extensions keep their meaning, upgrading language editions
    is easy.


Questions that don't seem to be answered by the spec.

  • what if you specify more than one language edition? Is that an error?

  • What if you have a language edition specified in your .cabal file, and another
    on in the {-# LANGUAGE #-} pragma of a module? Is that an error?

  • What if you are in GHCi, and you load a module that specifies a LANGUAGE? Is that an error?

  • Can different modules in the same package have different language editions?


What is the purpose of having a default language?

  • There is an on-by-default warning to warn if you don't specify a language. The way to suppress the warning is to specify a language edition, so everyone will. (Suppressing the warning with a -wno flag would be silly!) So that leaves the default language solely for GHCi; let's say that.

  • The warning can say "Please specify a language edition; I'm going to default to Stable2021 for this compilation".


"The one restriction on the expressive power of language editions is that build products of different language editions must be compatible."

We need to be very careful with statements like this:

  • All modules compiled with GHC(X) should be compatible.
  • Modules compiled with GHC(X) are not required to be compatible with those compiled with GHC(X+1). Binary interfaces (the ABI) may change between versions of GHC.
  • It's extra important to spell this out when you say "we can imagine that GHC becomes a set of programs that happen to share a binary; the choice of which program is chosen by the language edition". You want the "set of programs" to be compatible for a particular version of GHC, say GHC 9.10, but the set for GHC 9.12 will be utterly incompatible.

Let's spell this out, lest anyone misunderstand.


"Stable2024: Code compiled in the Stable2024 edition will be expected to compile (assuming stability of libraries) for 6 years, until the beginning of 2030."

I assume, though you do not say this, that you mean that ghc-9.10 -XStable2024, ghc-9.12 -XStable2024, etc will all compile and run the same programs for six years.

This is a bold claim, and one that I do not expect to hold in the near future. This is an aspiration (BG1) laid out in GHC Stability State of Play but there are multiple reasons why it is hard to achieve, including reinstallable base and Template Haskell.

Let's not make guarantees that we cannot (yet) keep.

One particular point on which the proposal is silent: does the language edition fix the
API of Prelude. I think it could and should, a baby step towards (BG1).


"Although language editions have wide authority, we must be tasteful in how they work."

This is (I think) discussing the quesiton of whether it is OK for -XGADTs to mean
enable GADTs in -XStable2024, but to mean "play Yankee Doodle on the speakers" in -XExperimental2024. No no no. Obviously we expect the meaning of an extension to be broadly stable. Rather than just saying "be tasteful" let's be much more explicit.

  • While a language edition may change the precise meaning of a langauge extension,
    • a language edition will usually change the extension to accept more programs, not fewer.
    • if two language editions accept the same program, it would almost always have the same semantics. That is, it would be unusual for a language edition to change the semantics of an extension.

I'm still baffled about -Wcompat. You say "So the Stable2024 edition of -Wcompat will warn abou
t features changing in Stable2027." But who is going to know in 2024 what will change in 2027?

I suggest omitting this. Just stick to the point that warnings can change with language editions.


"Once e.g. Stable2027 is released, new language features will not be available with the 2024 editions."

I don't like this at all. Why break exising programs, especially when the whole purpose of langauge editions is to allow you to say "just use the 2024 language version please"?

some of the language editions I'm proposing:

* ``Stable2024``: Code compiled in the ``Stable2024`` edition will be
expected to compile (assuming stability of libraries) for 6 years,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on line 19 it says "3 years"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe it's worth clarifying that "GHC releases until 2030 will continue to support Stable2024", since it's really about GHC releases and not the date when the code is compiled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused by this as well, but I think it's consistent: you have 6 years of stability, so after 3 years a new edition comes out and then you have 3 more years to migrate.

arguments that precede it.

- A cabal file will allow a new
field ``language-edition``, available both at top-level and in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use default-language?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FAQ: #636 (comment) :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the answer to the proposal.

+-----------------------------------------------+-------+-------+------+-----+------+----------+--------+-------+--------+--------+-----+---+--+------+--------+--------+--+--+--+----------------------------------+
|``RoleAnnotations`` | | | Y | Y | Y | X | | | | | | | | | | | | | | |
+-----------------------------------------------+-------+-------+------+-----+------+----------+--------+-------+--------+--------+-----+---+--+------+--------+--------+--+--+--+----------------------------------+
|``Safe`` | | N | N | N | N | | | | | | | | | | | | | | | [#]_ |
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safe Haskell is not compatible with Stable? Why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there have been increasing noises to remove the feature.

@simonmar
Copy link

simonmar commented Feb 8, 2024

Broadly in favour, although I think the proposal is too broad and could be significantly trimmed down.

  • I'm not convinced of the need for "semantic bundles" and furthermore those are orthogonal to the main thrust of the proposal. Suggest moving to a separate proposal.
  • I suspect that Student2024 will require a lot of discussion on its own, so again I'd move that to a separate proposal.

Simon says

I assume, though you do not say this, that you mean that ghc-9.10 -XStable2024, ghc-9.12 -XStable2024, etc will all compile and run the same programs for six years.

This is a bold claim, and one that I do not expect to hold in the near future. This is an aspiration (BG1) laid out in GHC Stability State of Play but there are multiple reasons why it is hard to achieve, including reinstallable base and Template Haskell.

Agreed, we need to make sure the details of how this will work are hammered out and we're sure it's feasible before we can commit to stability.

Today -XImpredicativeTypes means one thing. Under this proposal,
just saying -XImpredicativeTypes means nothing. I would have to
say "-XImpredicativeTypes as implemented in Stable2024".

I regard that as a significant disadvantage. Not a show-stopper, perhaps, but a significant disadvantage.

This needs a clarification, I agree. I would state it with a different emphasis: we must not change an extension's meaning for a given language edition, but we could change the meaning in a new language edition. We might well decide that changing extensions from one edition to the next is too confusing to contemplate (and I would agree!) but given that in the past we've felt it necessary to change the meaning of extensions from one GHC release to the next it seems plausible that we might want to make changes in the future. We could decide that those kind of changes can only be made by adding a new extension, which would be arguably better.

@goldfirere
Copy link
Contributor Author

Thanks for the detailed feedback!

@simonpj

The "Proposed change specification" does not define a language edition.

I'm not sure what you mean here. It appears to do this to me, with sentences like "Every file is compiled with respect to precisely one language
edition." and "A language edition can control almost all behaviors of GHC.". I could introduce the section with "A language edition is a thing as defined below." but I don't see how that's helpful.

We also have a brand new concept of a "bundle".

I've added some text drawing out the distinction between an edition and a bundle.

Today -XImpredicativeTypes means one thing. ...

Yes this is a downside. I've addressed this in the new FAQ section.

In contrast, if
language extensions keep their meaning, upgrading language editions
is easy.

True, but the premise of the hypothetical is false. That is, we have found the need to evolve the definition of language extensions somewhat often over the course of developing GHC. With this proposal, we actually make this easier -- as long as we preserve the old behavior with the old language edition.

Questions that don't seem to be answered by the spec.

I believe I have now answered these. See my recent diff.

What is the purpose of having a default language?

A good question. I've changed this a bit to say that you don't need to specify a language edition if compiling a bare Haskell file.

We need to be very careful with statements like [the one about binary compatibility.]

Indeed. I've clarified this a tiny bit in the main specification and then expanded on this in the FAQ section.

"Stable2024: Code compiled in the Stable2024 edition will be expected to compile (assuming stability of libraries) for 6 years, until the beginning of 2030."

I've clarified that this covers multiple GHC versions. But I stick by the claim. Firstly, it says "assuming stability of libraries", letting the stability of base to be resolved separately. But then it also says "expected": that is, users should expect this. We won't always deliver what they expect, but we should be rightly apologetic when we fail to deliver.

"Although language editions have wide authority, we must be tasteful in how they work."

Here you offer an expansion of "tasteful" -- but the expansion carefully does not offer any more guarantees than I did, with words like "usually" and "almost always". I've expanded this a bit in a FAQ, but I think expanding on this point in the specification is too much of a digression.

-Wcompat

I like the text there. One point that's perhaps too inferred is that the -Wcompat in Stable2024 will evolve between 2024 and 2027, gaining the warnings necessary to help the user upgrade to Stable2027. Is there another way you're understanding this that I'm missing?

"Once e.g. Stable2027 is released, new language features will not be available with the 2024 editions."

I continue to like this. There's nothing backward incompatible about it: it's just saying that users will have to upgrade to 2027 in order to get features invented in 2028, say. I've clarified the text a bit.

@simonmar

I'm not convinced of the need for "semantic bundles" and furthermore those are orthogonal to the main thrust of the proposal. Suggest moving to a separate proposal.

Maybe. But the way in which these are all tied together is that I want to change the user's view from "I have to think about all these extensions" to "I have to choose an edition and a few toppings." That is, I'm pining for a future where the average Haskell programmer does not even know about extensions. They just have a language that stably evolves!

I would state it with a different emphasis:

I like this text you've written and added it to the proposal. Thanks!

@TeofilC
Copy link

TeofilC commented Feb 13, 2024

One thing to consider here is that we have some settings off by default in GHC, eg, -fprint-explicit-runtime-reps, that improve compiler output for beginners at the cost of correctness/generality (ie, we don't output the most general kind).

I recently ran into this and though it was a bug: https://gitlab.haskell.org/ghc/ghc/-/issues/24025.

I think it's reasonable that we have these off by default. The average user doesn't need to see the full gnarly details of the type/kind system. But, as it stands, it's also difficult to discover this flag, and I assume there's others like it.

Perhaps this should be included in the FancyTypes bundle. Alternatively something like -fprint-explicit-runtime-reps could be enabled by default (or under Experimental) but disabled in the Student edition.

Another thing in this area is: https://gitlab.haskell.org/ghc/ghc/-/issues/21031. Namely this error message is suggesting enabling a somewhat advanced feature, but if we know that a user is a Student maybe we'd want to display an alternative message.

Comment on lines +18 to +19
One of the proposals is for ``Stable2024``, which will continue to be supported,
without change to the set of user programs accepted, for at least 3 years.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this say "6 years"? That's what the remainder of the document seems to suggest.

Suggested change
One of the proposals is for ``Stable2024``, which will continue to be supported,
without change to the set of user programs accepted, for at least 3 years.
One of the proposals is for ``Stable2024``, which will continue to be supported,
without change to the set of user programs accepted, for at least 6 years.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point.

@Bodigrim
Copy link

I can see a merit of Stable2024, even if I do not expect myself or my team to use it. I'm much less convinced about Student2024: a textbook will be long obsolete in 10 years because of libraries (including base) moving on, even if the core language remains the same. I think textbooks should just stick to Stable2024. I don't see a point of Experimental and Latest, seems just a distraction and a burden to maintain.

I'd suggest to introduce Stable2024 and see how it goes. If there is further demand and bandwidth to maintain, one might provide more editions. But at the moment I'm highly doubtful that there is manpower to uphold the promise for anything more than that. Making too many promises and failing to honor them is much worse than not making any promises at all.

I feel fairly strongly against providing variety of Preludes.

@tomjaguarpaw
Copy link
Contributor

I like these ideas a lot. For me, by far the most important language edition is Stable. It's essential that we have a way of specifying a setting and being confident that code that compiles with that setting will also compile in the future. I'd also suggest that a 6-year support window seems short for a stable language edition. 10 years sounds more desirable to me, though I don't really have a solid argument for picking that specific number.

@simonpj
Copy link
Contributor

simonpj commented Feb 14, 2024

It's essential that we have a way of specifying a setting and being confident that code that compiles with that setting will also compile in the future

Just to stress again: currently we don't have a way to promise that old code will compile with a new compiler, because of (a) Template Haskell and (b) lack of reinstallable base. (Are there any other blockers?)

That remains our aspiration (see (BG1)); and language editions helps us along the way (a little bit -- our existing extensions mechanism is almost as good, except for rare cases when we change the meaning of an extension in a non-back-compatible way). Overall, though, I feel as if we have 10km to go, and this proposal moves us on 1km. Helpful, but we should not let our users believe that language editions will do more than they do!

@tomjaguarpaw
Copy link
Contributor

Just to stress again: currently we don't have a way to promise that old code will compile with a new compiler, because of (a) Template Haskell and (b) lack of reinstallable base. (Are there any other blockers?)

Yes, agreed. Perhaps I should have said "code that compiles with that setting will not fail to compile in the future because of changes to the language specifically". GHC can't guarantee that code will compile in the future; it can guarantee that if code doesn't compile it wasn't because of a language change.

Copy link
Contributor

@adamgundry adamgundry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @goldfirere for all the effort you've put into this proposal. While I don't agree with the direction you set out, I'm grateful you are driving this conversation forward, and I hope we can find common ground. To try to summarize my position (and with apologies if I've missed anything while reading the text):

  • The overall impression I get from the proposal is that it is much too dictatorial. It is full of restrictions that exist not because the combination of features cannot be supported, but because as language designers we have decided to make them harder to access. It is very "all-or-nothing": either opt in to the proposed stable feature set in its entirety, or choose an entirely experimental option.
  • In practice, it seems like it will be very common for code to mostly conform to the stable subset, then have a few modules where something more complex happens. I think we should give users the ability to say "most of my code is stable" and make selective exceptions where they are willing to take the risk. Otherwise many packages will not be able to benefit from stability guarantees, or will need to be contorted to receive them.
  • More generally, I think we should help our users to make good decisions about which combinations of features are suitable for their use case, but trust them to do so and give them flexibility to override the defaults where they have reason to do so. We shouldn't assume we know better than them about which features are suitable for their use case based on a few very broad generalisations.
  • Bundling extensions/warnings into larger combinations that make sense as a group seems like a reasonable idea in principle, though the details of the bundles need some thought, and I think we should do this only where there is a clear reason to enable (and document!) them together as a unit. (For example DoSyntax is unconvincing: just turn on e.g. Arrows individually!)
  • I'm skeptical of the complexity that arises from having extensions/bundles mean different things depending on the language edition.
  • I think we should include features that are not language extensions/warnings in our classification of "stable" vs "experimental" features (see Which GHC features are experimental? #635). For example, some optimization flags are clearly experimental, and some are clearly more stable. But it isn't obvious to me that tying this to a language edition makes sense.
  • I don't think it is feasible to agree this proposal in time for GHC 9.10. Thus I suggest we release GHC 9.10 with GHC2024 as already agreed.

* A new warning
``-Wmissing-language-edition``, will inform users
that they should specify a language edition. This warning will be
off by default in GHC, but it is expected that cabal will turn it on.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cabal packages already specify a language edition in all cases (usually via default-language, but if that is not specified, then Haskell98 - see haskell/cabal#9668). So I don't think we need this part about Cabal turning on the warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That ticket still has some debate about this point. I favor keeping the text as written in this proposal. If cabal in practice always specifies a language edition, then turning the warning on always would have no effect, anyway.

language edition will be ``GHC2021``.

* A new warning
``-Wmissing-language-edition``, will inform users
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #632 I suggested calling this -Wunspecified-language, and put it in -Wcompat. I don't really care which name we pick, we should just end up landing on something consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I've added a note about this in the Alternatives.

Comment on lines +1051 to +1061
A: Because of the word "default". That word implies that this is the
language for all modules except those that specify some other
language, but I think the cabal file should be authoritative about
language edition. That is, I want to be able to know that a package
is stable just by looking at the cabal
file. Maybe this is problematic if someone really wants a package
mixing language editions? I suppose if that is a use case we want to
support, there could be ``language-edition:
as-specified-in-each-module`` or something. Alternatively, we could
say this field is not required by cabal. Regardless, I don't think
the field should specify an overridable default.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is extremely unconvincing, in my view. So if someone wants to use an experimental feature (say DefaultSignatures) somewhere in one module, they have to mark the entire component as experimental? I realise you want to move away from the fine-grained story we have at present, but this seems unnecessarily restrictive. Moreover it generates busywork for Cabal. Keeping default-language and allowing it to be overridden on a per-module basis would be much better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could agree with the move not to introduce busywork around the naming of the flag. But actually I think it's problematic to be able to override the language edition locally. If I see a Haskell package whose cabal file says Stable, then I want to know that the package really is stable. So I stick by the text I've written above, including the proposed escape-hatch of as-specified-in-each-module (or something -- I actually think my proposed UI here is poor).

Comment on lines +186 to +191
- Any new language features invented once e.g. ``Stable2027`` is released
will not be available with the 2024 editions. That is, if we introduce a new
feature ``-XDependentTypes`` in 2028, then enabling ``-XDependentTypes``
with ``Stable2024`` (or even ``Experimental2024``) will be an error.
This policy encourages users to upgrade their editions in order
to access GHC's new features.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it will be really annoying if one has a large package using Stable2024, and want to start using features from Stable2027 in some modules, but Stable2024 -> Stable2027 is not a seamless upgrade, and you can't vary editions within a single component...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm.... interesting point. Yes I think it would be nice to allow this. Specifically, I could see allowing a language-edition override if the overriding language edition is a later version of the same series. In your proposed scenario, the cabal file would say Stable2024 (meaning: this package will continue to compile until at least 2030), but individual modules could have Stable2027 (meaning: this package will continue to compile until at least 2033). This works because the stability guarantee of Stable2027 is strictly stronger than that of Stable2024. It doesn't work as nicely for overriding a Stable2024 promise in a cabal file with an Experimental module.

How does this sound? I went to just incorporate this into the proposal, but it's not all that easy to do so, as doing it right probably requires having a notion of Stable separate from 2024. (This separation has been suggested elsewhere, too, but I didn't see what new flexibility / expressiveness the distinction granted -- until now.) I'll leave this as an underspecified Alternative for now, but if we end up settling in agreement here, I'll rewrite the proposal accordingly.

Comment on lines +213 to +214
Unlike language editions, a user may specify any number of bundles
when compiling a file. In order to simplify their processing, a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a bundle be specified in a .cabal file? Is it possible to negate a bundle (e.g. -XNoFancyTypes)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to specify a bundle in a .cabal file yes. Maybe also -XNoFancyTypes? I guess -XNoFancyTypes means that individual modules would be unable to turn on the features in there? I'll add this as an open question -- could be convinced in any direction here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess -XNoFancyTypes means that individual modules would be unable to turn on the features in there?

Ideally not: this is inconsistent with how extensions work and in conflict with the general principle that you should always be able to override earlier stuff.

Comment on lines +1130 to +1135
It is time for the ``Safe`` ecosystem to be dismantled. We should warn
on the use of any of the ``Safe`` language extensions, but otherwise
ignore them. With the introduction of the language editions proposed
here, GHC will allow a ``Safe`` module to depend on any module, thus
allowing for a transition period, even if some libraries eagerly adopt
these language editions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I think I said before, while I agree with this, I think it needs more than a footnote.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This proposal does not propose getting rid of Safe. It just doesn't include Safe and friends in the various language editions.

Comment on lines +999 to +1001
* ``Stable`` and ``Experimental`` are central to this proposal; ``Student``
and ``Latest`` less so. I can see forgoing either of these two. I like
keeping them, but maybe others disagree.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see very little reason to have Latest. We moved away from -fglasgow-exts a long time ago. Users can ask for the specific experimental/latest features they need, rather than getting an ad hoc collection of most of them.

I don't have a clear opinion about Student, although it feels rather arbitrary to me. I think it may be more confusing than helpful to have the available language features differ from Stable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't much care about Latest. I still think I like it (though it has gotten a negative reception) -- mostly because I want to move away from worrying about extensions at all, and then Latest just gives me all the good ones. :) I think a key difference between Latest and -fglasgow-exts is that we should feel free to evolve Latest arbitrarily.

For Student, I think the real power is in the ability to tailor error messages appropriately, though perhaps that is not enough of a motivation.

Copy link

@michaelpj michaelpj Feb 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all Student amounts to is more verbose error messages, then I don't see why that couldn't just be a flag. --include-error-explanation. Or perhaps if we're learning from Rust we just want ghc --explain <error-code>?

Comment on lines +178 to +184
- A cabal file will allow a new
field ``language-edition``, available both at top-level and in
build-product stanzas. This will specify the language edition. To
support backward compatibility, this will use the ``default-language``
setting if that is available, and omitting the ``language-edition`` will
use the default. At some point, it is expected that ``language-edition``
will become required.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a process point of view, we can't definitively specify Cabal features like language-edition here, though we can certainly suggest them to the Cabal developers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed absolutely.

proposals/0000-language-editions.rst Show resolved Hide resolved
proposals/0000-language-editions.rst Show resolved Hide resolved
@blamario
Copy link
Contributor

There are some things I like about the proposal, namely the concept of language editions with the potential to encompass the built-in libraries, and the aspiration to improve the forward compatibility. There are others that I'm very suspicious of, like the feature bundles.

I concur with the existing opinions that the proposal should be broken up so we can discuss and vote for each aspect separately.

Re: bundles

Unless I missed something, a bundle would in effect be no different from a language extension that implies a bunch of other extensions. We already have those, as well as a process for adding new language extensions. If you want a new language extension named FancyTypes which implies several other extensions, just write a proposal for that. I see no reason to invent a whole new category for the thing.

@goldfirere
Copy link
Contributor Author

@TeofilC I'm sympathetic here, and I agree that these flags are hard to discover. I started to write some notes in the proposal for how it might help this situation, but I ended up stopping: I don't quite think FancyTypes should change the output in this way, because the verbose output is really verbose, and most users -- even fancy ones -- won't want it. On the other hand, it might be sensible to bundle together -fprint-explicit-runtime-reps with other verbose-printing flags, but I don't see the bundle changing the language GHC accepts.... so it doesn't quite fit with this proposal (to me).

@Bodigrim I think we need some alternative to Stable2024 for this to hold water. But I agree we don't need several. I suppose a sensible thing here would be to have Stable2024 and, say, Unstable2024, where the latter means exactly the former, but allows users to enable extensions that are unstable. But actually that's exactly what Experimental2024 is meant to mean... maybe we just rename it? I'm fine with dropping the others.

@simonpj @tomjaguarpaw Yes this only affects GHC, not the libraries. Does this go far enough to be worthwhile? I think that's a little bit the wrong question here. In order to achieve the goal of having Haskell code live for a long time, we need a stable GHC and stable libraries. Fixing either one alone won't deliver the goal. But certainly the fact that one alone doesn't deliver the goal shouldn't stop us from fixing one alone, because that approach gets us nowhere. As to whether GHC or the libraries are the primary culprit in instability, I think the answer depends strongly on whom you ask: library folk tend to say that GHC keeps changing under their feet, and GHC folk say the libraries keep moving around. It doesn't matter in the end: let's fix both, which we're endeavoring to do.

@adamgundry Thanks for the detailed feedback! Very constructive.

much too dictatorial

Yes! That's the intent! :) I've come to think we've been doing our users a disservice by giving them fine-grained control over details many of them won't have mastered. We still should give control to users who want that (this proposal does not suggest actually removing the language extensions), but the default should be that we, the experts, can lay out reasonable choices that should suit most use-cases.

Of course reasonable people will differ here, but if you're calling this dictatorial, then I've expressed myself clearly. :)

It is very "all-or-nothing"

That's intentional, too. Stability is all-or-nothing. That is, either the language is stable, or one aspect of it might change -- one aspect is enough. The plan here does give users a way out: they just have to say Experimental and then they can add extensions to their heart's content.

In practice, it seems like it will be very common for code to mostly conform to the stable subset, then have a few modules where something more complex happens. I think we should give users the ability to say "most of my code is stable" and make selective exceptions where they are willing to take the risk. Otherwise many packages will not be able to benefit from stability guarantees, or will need to be contorted to receive them.

That might be true. Maybe, then, we should imagine settings in cabal that say "mostly stable", which allows individual modules within a package to use experimental features, where the maintainer of the package is responsible for being stable-in-practice. Yet this is problematic, because now if I want to depend on such a package, how do I know whether to trust the maintainer's commitment to stability? We'd need a network of trust, not unlike what we have with -XSafe. This will be challenging.

I think, in the end, we need to be prepared to give something up, in the name of stability. Or, rather, package authors need to -- it would be their choice.

We shouldn't assume we know better than them about which features are suitable for their use case

That's absolutely true. But we likely do know better than users which features are more stable. If they want to go beyond that subset, by all means they should -- just by saying Experimental.

Bundling extensions/warnings into larger combinations that make sense as a group seems like a reasonable idea in principle, though the details of the bundles need some thought, and I think we should do this only where there is a clear reason to enable (and document!) them together as a unit. (For example DoSyntax is unconvincing: just turn on e.g. Arrows individually!)

Yes, I may have overreached here. DoSyntax is pretty unconvincing indeed. FancyTypes seems better. I've added a note in Alternatives and in the introductions of these two.

But it isn't obvious to me that tying [control of possibly-experimental optimization flags] to a language edition makes sense.

Why not? A language is a syntax + static semantics (type system) + dynamic semantics (run-time behavior). The optimization flags control an aspect of run-time behavior. Seems like part of a language to me. (To be clear, I see why some would differ here -- I'm trying to take an opinionated stance.)

I don't think it is feasible to agree this proposal in time for GHC 9.10. Thus I suggest we release GHC 9.10 with GHC2024 as already agreed.

Yes I tend to agree. I don't think this should be rushed.

@blamario

I concur with the existing opinions that the proposal should be broken up so we can discuss and vote for each aspect separately.

The parts are designed to make for a cohesive whole -- including the bundles. The main payload in that "cohesive whole" is a movement away from thinking about language extensions (whose consequences for maintainability, training of new developers, stability, etc., are hard for most Haskellers to keep up with) to just a few language editions and bundles, which can be understood more easily. The committee may wish to partially approve this proposal, and perhaps they'd slice this in a way that preserves the intent. But I do not see myself taking the time to make this revision before a vote, say.

Unless I missed something, a bundle would in effect be no different from a language extension that implies a bunch of other extensions.

A bundle can do more than just enable other extensions. It can change warning settings and, potentially, the text of error messages. (As usual, we do not formally treat error messages in these proposals.)

While it's true that I could propose each bundle separately, I think they make the most sense taken together, and with the language editions.

In the past (specifically around changes to type variable scoping), we've had a bunch of separate proposals. They all were trying to talk about some of the same things, though each was notionally independent. Then I was asked to coalesce them all, so they could be understood together; this is #448. Then, in the debate for #448, some comments said that it's all too big and we should consider the pieces separately again! (Those comments did not carry the day.) I conjecture that this kind of reformatting is not a good use of our time. Instead, nothing stops the committee form partial acceptance, if that's where things are headed.

----------

The primary motivation behind the use of language editions is that they
can succinctly inform GHC what kind of user it's faced with, so GHC
Copy link

@michaelpj michaelpj Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the focus on the user is good, but what @tomjaguarpaw 's comment points out is that we haven't worked out what the (sorry) user personas are. What does the user of Stable2024 want or expect? In particular, I think there are a range of possible expectations:

  • About their own code:
    • Is guaranteed to be stable on later versions of GHC
    • Is likely to be stable on later versions of GHC
    • Can/cannot have those guarantees reduced if they make exceptions
  • About their dependencies:
    • No assumptions
    • All/some are guaranteed to be stable on later versions of GHC
    • All/some are likely to be stable on later versions of GHC

Here are some possible combined positions:

  1. "I want tools to help me ensure that the code that I write will be stable on later GHCs, but I want the ability to take things into my own hands and make exceptions if that is useful. I want to be able to communicate stability levels between packages, so that I can have a level of assurance that my dependencies are stable, and communicate my own stability promises downstream; but I am okay with there not being hard guarantees (since I expect everyone to make exceptions here and there)"
  2. "I want everything to be rock solid. I have no interest in using cutting-edge features at all, and will jump through as many hoops as necessary to avoid it. I also really don't want to deal with dependency issues, so I only want to use dependencies that are rock solid as well"
  3. "I mostly don't care that much, but it would be nice if the defaults pushed packages towards using more stable features so things were less likely to break"

Similarly for students:

  1. "I have no idea what I'm doing. I just want it to work so I can clear this class. I'm not going to do anything the teacher doesn't tell me to and I want no surprises."
  2. "I love Haskell! This is great! I immediately want to play with all the toys! It's day 2 and I'm trying to implement an iceland_jack tweet!"

I'm unsure who the users of Experimental or Latest would be. I guess people who don't care about stability at all. But most people care a bit. In particular, here is one more persona I think we might want to worry about:

  1. "I really need to use LinearTypes. That's kind of the point of this project. I know that opens me up to a bit of breakage, but that doesn't mean I want everything to break. If anything, I want to keep the rest as stable as possible to compensate for my big stability hit in using LinearTypes!"

The reason that I think this is important is that while I appreciate the desire to be opinionated, I think there's a risk that we fixate on a particular manifestation of the "cares about stability" persona or the "is a student" persona, and that can also be pretty harmful to the other people!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this really helpful.

For the different stability goals: I continue to claim that Experimental2024 is a viable "escape hatch". Maybe we just need TryingToBeStable2024 which is technically identical to Experimental2024 but socially distinct? (That's a bit of a joke.) That is, in order to suit the "rock solid" camp, we need Stable2024 to be completely reliable (or as close as we can make it). The other stability folks can work with Experimental2024 and social cues. I do like the LinearTypes example. But that author still has to make a choice: do they want rock-solid dependencies (Stable2024) or not (Experimental2024)? Anything beyond rock-solid requires the social cues again.

I continue to become less enthusiastic about Student. I agree that the error-message aspect could be controlled using a different, less heavy-handed mechanism.

For Latest, the persona is simple: "I just want to experiment with Haskell. I'm not going to publish this." Except that maybe they do, in the end, for use by other Latest people.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the different stability goals: I continue to claim that Experimental2024 is a viable "escape hatch".

I agree this is fine if we have per-module language editions (although I continue to be confused about how that works given that specifying the edition twice is an error and GHC can have some flags that it must process before seeing the module).

But that author still has to make a choice: do they want rock-solid dependencies (Stable2024) or not (Experimental2024)?

Here again we're talking about dependencies. I think this is worth making really explicit because I think it needs quite a different story. The workflow for making my code stable is straightforward enough: I set Stable2024 as the language edition. But how am I going to know that my dependencies are stable? I don't see how you can provide any actual guarantee there without doing something like Safe Haskell. So it's really quite important to the scope of this proposal a) whether saying anything about dependencies is in scope, and b) whether we want hard guarantees or only social guarantees; all of which depends on which personas we want to satisfy. So I would really like to know your answers!

For Latest, the persona is simple: "I just want to experiment with Haskell. I'm not going to publish this."

Why would they not use Stable? "I'm messing around" is a reason to not care, not a reason to actively desire Latest. If the people who don't care will be satisfied with Stable, that's simpler. So who wants Latest?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent point. This proposal offers no enforcement mechanism around dependencies. I think it probably should. Specifically, I think there should be an option stable, specified in a cabal file, that requires all dependencies to also be stable. It would be an error to specify stable and not have a StableXXX language edition selected. For simplicity, I'm suggesting here that the cabal flag not specify the year; that can be done in the language edition.

  • A cabal file (or individual module) can specify Stable2024 without specifying stable. That's fine. It means that the package or module is locally stable but not part of the guaranteed-stable ecosystem.
  • This plan is much simpler than the plan around Safe Haskell, because there is no escape hatch. (Cue the tomatoes, which I deflect below.) That is, there is no equivalent to Trustworthy. Simple straightforward transitive dependencies. No exceptions.
  • (Here is where I deflect tomatoes.) The reason this works is that we can all program without the use of unstable extensions. Look at the "N"s in the Stable column in the proposal. The key question: can we live without these extensions? In particular, can we compile major packages (including base!) without these? Let's look.

These N extensions affect only concrete syntax; other concrete syntax is easily available: AlternativeLayoutRule, AlternativeLayoutRuleTransitional,CUSKs, DeriveAnyClass, NondecreasingIndentation, NPlusKPatterns, NumDecimals, OverlappingInstances, OverloadedRecordUpdate, ParallelArrays, RelaxedLayout, TransformListComp, TypeAbstractions

These N extensions do nothing or are subsumed by other extensions: AutoDeriveTypeable, NullaryTypeClasses, TypeInType

These N extensions actually do increase expressiveness, but in ways we actively do not want: DatatypeContexts

These N extensions actually do increase expressiveness, but we have better ways to say it now: DefaultSignatures

These N extensions are properly experimental: LinearTypes, RequiredTypeArguments, StaticPointers

These N extensions are part of Safe Haskell: Safe, Trustworthy, Unsafe

Safe Haskell aside, the question really is: do we want stable packages to depend on other packages using DatatypeContexts, DefaultSignatures, LinearTypes, RequiredTypeArguments, or StaticPointers? Those are really the only extensions in question. DatatypeContexts have been deprecated for more than a decade. That said, they're not unstable; I could see allowing these in Stable2024 if we want to extend the deprecation period. (Though, actually, I think DatatypeContexts can be perfectly simulated by pattern synonyms. So maybe we could indeed excise them from the language.) DefaultSignatures are more controversial. We should probably do a little impact study to see how hard it would be to get rid of them (in favor of the more flexible DerivingVia). I'm content to allow these into Stable2024. The other three really are experimental, and I don't think we should yet allow them into a stable ecosystem. (TypeAbstractions is both experimental and has syntactic workarounds.)

Safe Haskell is a different ball of wax. Maybe the best approach is to deprecate them, but still allow them in Stable2024, albeit that they would have no effect and cause warnings.

The bottom line for me here is that I think we can make this stable ecosystem without need a trust mechanism. It's the trust mechanism that makes Safe Haskell hard. It's necessary there. But I think it's not necessary here.

So who wants Latest?

I do! :) That is, I'd specify Latest in my little Haskell experiments. But I easily admit that I don't need to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A cabal file (or individual module) can specify Stable2024 without specifying stable

That's great. That's the way I'll use it (and the way I expect the vast majority of users would want to use it)

I think there should be an option stable, specified in a cabal file, that requires all dependencies to also be stable. It would be an error to specify stable and not have a StableXXX language edition selected

To what extent is this a GHC concern? The cabal team are welcome to implement that, of course, but that's quite independent of what the GHC steering committee decides (unless you're suggesting some sort of joint proposal).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposal offers no enforcement mechanism around dependencies. I think it probably should. Specifically, I think there should be an option stable, specified in a cabal file, that requires all dependencies to also be stable.

What problem is this intended to solve, and for which class of users? How often are users accidentally depending on libraries that break because they used one of these extensions?

My impression is that the main reason dependencies lead to stability problems is not principally due to language changes, but library changes (and #636 (comment) seems to back this impression up). So having a stable option that increases the chances of language stability throughout the dependency stack, but doesn't do anything about libraries changing their APIs, may not have the impact we'd like.

Copy link

@michaelpj michaelpj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having read this proposal again, I continue to think that the basic idea of editions is a good one. But I think the current proposal is maybe shooting for too much.

Let me propose a much more cut-down version.


Language editions are cut based on years. They are implicitly stable: they don't change. Thus GHC2024 simply means "Haskell as compiled by GHC in 2024", forever (until it is retired). These are the only language editions. If there is no language edition specified, then the latest behaviour is used and a warning is emitted. As in the current proposal, language editions can in principle affect (almost) arbitrary behaviour in GHC. In particular: which extensions are allowed (stable ones), which are default, etc.

(Exception: behaviour that is not visible to users can change. In particular, a given GHC should produce interface and object files that are compatible with each other no matter the language edition: it would be very bad for language edition to dictate interface file format! This probably applies to other non-visible behaviour too...)

Language editions can be specified more than once, the latest takes precedence. (This adds some complexity in flag processing, but it's hopefully okay: we just have to do an initial scan to find all the language edition flags and pick one before we start on the rest.)

A module can be marked as Unstable. This gives access to unstable extensions and flags, whose behaviour depends on the current GHC. It does not unset the language edition, which still controls the meaning of stable features.

Stability information for a module is written into the interface file for a module. This is:

  • Stable: if the module was compiled with a language edition, and not marked as unstable, and all of its dependent modules are stable.
  • Unstable: otherwise. (Maybe we add a Trustworthy-like override)

(This omits a remaining source of instability: the removal of language editions eventually. We might want to also record the oldest language edition used transitively in order to spot this?)

A warning is emitted when depending on an unstable module.

(A variant: record a list of the transitively used unstable module, allow whitelisting some of these. Might be useful if some core library has an unstable part that renders everything depending on it unstable)


I think this covers all of the users!

  1. "I want absolutely everything to be rock-solid/I'm a textbook writer": use a language edition, make the warning for depending on unstable stuff an error.
  2. "I want to make my code stable, but I'm not dogmatic about it": use a language edition, mark stuff Unstable as needed, tell people from the first group to go away if they complain about your modules being inferred as unstable (or join them)
  3. "I don't care, whatever": don't use a language edition, scatter Unstable around freely, turn off the warning for depending on unstable stuff
  4. "I'm a normal student": use a language edition, don't use Unstable, turn on the flag for more verbose errors
  5. "I'm a keen student", see 3

I also think this is simpler. Editions are always stable, they always just reflect a snapshot in time, that's it! I think this is nicer for GHC devs too: you can have one check that amounts to "are we pretending that the time is earlier or later than X?" which you can use to guard every behaviour, rather than having to separately reason "should this go in edition Y?" on a case-by-case basis.

We have just one override mechanism for getting out of the stability where you want to.

The complex bit is the Safe-Haskell-like stability tracking of dependencies. I think we need this for the rock-solid people, and I think it's easier than Safe Haskell since it really should be perfectly inferrable: you just need to look at the flags used in a module, no trickiness with looking at the actual code!


I also could say more about feature bundles, but TBH I think they should be in a separate proposal. I think they're interesting in this context because they're another example of why bundling behaviour together in various ways might be useful, but I think we could decide on language editions independently, and frankly I'm already out of energy just from thinking about editions!


* **Simplicity**. The goal is that a vast majority of our users will be
able to specify a language edition, and that's it. No extensions. No
warning flags. This simplifies what a user needs to think about when

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"No warning flags" seems optimistic. My experience is that people have a lot of opinions about what warnings they do and don't like.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimistic: yes. But I actually think it would be interesting and worthwhile to understand what warning flags are needed. That is, enabling a warning moves responsibility from the programmer to the compiler (e.g. the programmer no longer needs to worry about the completeness of their patterns), or establishes some property of a program (like the ability to find a binding site of a variable using a simple analysis). So if users are enabling more warnings, I'd want to understand what property they're worried about, so we can perhaps design our system better. That said, we probably won't do this (it's hard). I'm fine with warning flags. I just think that each warning flag is a small message that we've slightly mis-designed something.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if users are enabling more warnings, I'd want to understand what property they're worried about, so we can perhaps design our system better.

This sounds good in the abstract but I'm not sure I buy it in the concrete. Consider unused variable warnings. Users want them on when they want to spot any little thing that might be wrong with their code, or tidy it up for others to read. Or, at other times they may not care about such things and want to turn them off. I don't see a way to design the system better here: users just care about different things at different times.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @michaelpj here. Warning flags are useful because they allow the compiler to offer a menu of different analyses with different trade-offs, so the user can choose which they need at a particular point in time (which may well change depending on where they are in the development cycle of a particular program, or just depending on what they are currently interested in doing). This includes warnings about essentially stylistic or otherwise controversial choices (e.g. Wx-partial), where the alternative is for the compiler authors to be forced to pick a side in the controversy (either by making a warning mandatory or by not supporting it).


- A language edition can be specified using the extensions syntax, by
passing e.g. ``-XStable2024`` on the command line or putting
``Stable2024`` in a language pragma.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not an extension (which it very much seems like it isn't), then I think it should be set differently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I find I agree here. I will change this.

* In GHCi, the default language edition will be the latest ``Stable``
edition. ``-Wmissing-language-edition`` will be off by default.

- In GHCi, the top-level can have a distinct language edition from loaded

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is again an "everywhere" vs "local" clash. If we want a language edition to be like having a different compiler, it should always load files following its own expected edition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't follow here. A key requirement is that the language editions are inter-compatible. So having GHCi be able to load code from other language editions seems both easy to support and useful, to me.

(or existence) of other flags can depend on language edition. While
we will not implement it this way, we can imagine that GHC becomes
a set of programs that happen to share a binary; the choice of which
program is chosen by the language edition.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we see an implicit lean towards "everywhere"

The one restriction on the expressive power of language editions
is that build products of different language editions (within the
same version of GHC) must be
compatible. We expect the Haskell ecosystem to contain packages

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we see an implicit lean towards "local"

Comment on lines +213 to +214
Unlike language editions, a user may specify any number of bundles
when compiling a file. In order to simplify their processing, a

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess -XNoFancyTypes means that individual modules would be unable to turn on the features in there?

Ideally not: this is inconsistent with how extensions work and in conflict with the general principle that you should always be able to override earlier stuff.


* E: the warning is an error by default

* W!: the warning warns by default and cannot be turned off

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will be unsurprised to hear that I hate this idea :) The user should always be able to change the behaviour if they want. Telling them they can't (when it is clearly possible) is mostly just insulting and aggravating.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this particular user has asked for this restriction, by saying Stable2024. If they want to disable the warning, just stop asking for the restriction, by saying Experimental2024.

Do you hate that Haskell rejects the following program?

stuff = [3, "a", 10, "b", 21, "c"]

odd acc1 acc2 [] = (acc1, acc2)
odd acc1 acc2 (x : xs) = even (acc1 + x) acc2 xs

even acc1 acc2 [] = (acc1, acc2)
even acc1 acc2 (x : xs) = odd acc1 (x : acc2) xs

(sum, letters) = odd 0 "" stuff

Executing this is "clearly possible": it results with sum being 34 and letters being "cba", if I haven't made a mistake. But of course GHC will reject. And we want GHC to reject, because we have opted for a statically typed language. Similarly, a user opting for a stable language should be able to get what they want.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to use Experimental2024. I want to use Stable2024 with the warning turned off, which is a) pretty reasonable, since it clearly doesn't affect what the compiler can do) and b) something that I am used to from literally every other compiler I have ever used.

Similarly, a user opting for a stable language should be able to get what they want.

What has stability got to do with warning flags? Or being a student? Turning off a warning does not make my code unstable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If, for example, there was a warning whose behaviour was unstable, then perhaps we should not allow turning that warning on in Stable code... except again, we have disclaimed warning stability as a goal.


* W!: the warning warns by default and cannot be turned off

* E!: the warning is an error by default and cannot be turned off or made into a warning

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

when it is appropriate to migrate to the new style.

Includes the following: ``FieldSelectors``, ``NoPolyKinds``, ``StarIsType``, ``-Wno-deriving-typeable``,
``-Wno-prepositive-qualified-module``, ``-Wno-type-equality-out-of-scope``

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's interesting that this is the only bundle that includes both extensions and warning settings. If it were not for this I would be inclined to say that maybe what we want is warning groups, e.g. so you could write -Werror=incomplete and toggle all the errors in the "incomplete warning group" into errors. I still think that's a plausible alternative, though - if Classic is the only example of such a bundle then maybe it would be cleaner to have extension groups and warning groups separately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. But I continue to think that the division between language extension and warning is very arbitrary, and so separating out these two from each other seems unhelpful, to me.

``RequiredTypeArguments``, ``RoleAnnotations``, ``TypeAbstractions``, ``TypeData``,
``TypeFamilies``, ``-Wterm-variable-capture``.

* ``DoSyntax``: This enables extra syntactic support around ``do``-notation. Someone

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the stability story for bundles? Presumably if we add a new kind of extension that affects do syntax, then we would want to add it to DoSyntax, but we don't want to change the meaning of old code. So I think the language edition must also control the meaning of bundles?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course! But I agree that this is not spelled out in the proposal. The proposed bundles all have the same meaning regardless of the proposed edition. This might not be true in the future. I will clarify.

@mpickering
Copy link
Contributor

mpickering commented Feb 28, 2024

The issue of stability has been one of the primary goals of the GHC maintenance team over the last few years. We now have extensive regression testing of the compiler against a large set of packages, including memory performance and several important library testsuites. This has also led to a real culture shift within the framework of GHC development where we are constantly assessing whether and why any breakage should happen.

I think the proposal needs to tackle the implementation question much more seriously. At the moment it states:

This will be easy to implement; I volunteer to do so.

The implementation cost of the proposal stretches much further than implementing the editions as described in the proposal. The cost only begins at this point, it is then for the next 10 years that ALL changes to the compiler must be analysed against ALL existing language editions to decide whether ANY change violates the strong contract expressed in this proposal.

  • How is it verified that a change violates the Stable2024 guarantee?
  • Who is responsible for enforcing that GHC MRs are abiding by this change?
  • If the person identified is a volunteer, then how is it ensured there is a volunteer available for the entire period who is willing and able to timely review all GHC MRs to ensure they uphold the standards?
  • If the person or group identified is a paid group or individual then what is the funding strategy for financing the additional work this change introduces?

The proposal also side-steps the issue of library stability. It seems that you have to also at least include the stability of the base library in such a proposal for it to have any impact. If someone is able and willing to modify their program to reflect changes in base or another built-in library then they should also be able and willing to modify for compiler changes.

In my opinion it's really important to consider these kinds of questions when discussing stability as stability as a goal is hollow without the resources to back it up over a prolonged period.

@simonpj
Copy link
Contributor

simonpj commented Feb 28, 2024

Some thoughts:

  • The substantive change in this proposal is that a language edition is more than simply a collection of extensions. In addition:

    1. A language edition can change error messages and default warnings.
    2. A language edition can (completely) change the meaning of an extension.

    I support (i), but I am very doubtful about (ii) as I said up-thread.

  • A second contribution is perhaps a list of which language extensions are "stable"? Yes that list is valuable; but does this proposal then subsume GHC proposal 635? Or could we fork off that discussion to 635 and deal with the rest here?

  • I am unpersuaded about "semantic bundles". As the proposal says "The big difference between a bundle and a language edition is that a file is compiled against exactly one language edition, but it can be compiled against an arbitrary number of bundles." So a language edition is just a slightly crippled bundle? I think we have enough complexity. Let's just stick with language editions for now.

  • I am very unpersuaded that making language editions make things unavailable is a good plan. Just because my baseline is -XStable2024 why should I be prevented from using -XLinearHaskell? This is too nanny-state-ish.

  • "Code compiled in the Stable2024 edition will be expected to compile (assuming stability of libraries) under that same language edition on all GHCs release for 6 years, until the beginning of 2030." We have had this aspiration for some time. It is a good goal but currently it is out of reach for reasons described at that link. Moreover why 6 years? My goal is that if it compiles with GHC(N) then it should compile with GHC(N+k), full stop. But as I say, even if k=1, we cannot currently make that guarantee.

    This proposal neither contributes to, not detracts from, that goal. It is a perfectly good goal today: if your code compiles (with a given set of language extensions) with GHC(N) it should compile with GHC(N+1). But it's a bit misleading to have these claims made so prominently as part of the proposal. I think this is partly what Matthew is concerned about.

  • Although we cannot technically deliver on this yet, we should say that our goal is that a language edition also fixes a particular API for Prelude, all base modules, and preferably all Template Haskell modules. This is key for delivering stability; yet the proposal barely mentions the issue.

  • There is some discussion in the discussion-thread about transitive stability, but I see nothing in the proposal about that. I'm not clear what is being proposed here beyond (GR1-3) of GHC's stability principles, which carefully frames (GR1-3) as goals rather than guarantees.

@simonpj
Copy link
Contributor

simonpj commented Mar 7, 2024

One other question. I think it is arguable that:

  • Any StableXXXX language edition should
    • depend only on language extensions designated as Stable
    • not require dependency on the ghc-experimental library

If we intend these properties to hold, this proposal would be a good place to articulate them.

@tomjaguarpaw
Copy link
Contributor

tomjaguarpaw commented Mar 7, 2024

I'm glad that there's enthusiasm in the community for increasing the stability of our language, libraries and tooling. I think that is an important component of making Haskell more suitable for wider adoption. Stable language editions can contribute to the goal of stability but I have concerns about the particulars of this proposal.

Firstly, I'd like to challenge the notion that GHC per se is a major cause of instability in the Haskell ecosystem. This is an empirical issue so we ought to look at what has happened in practice. In fact I don't think we can improve the state of stability in our ecosystem without a careful empirical analysis. Stability is not the kind of issue we can improve through abstract technical definition. Improvements have to follow from practical observation.

Here's an example: @angerman has written a helpful list of everything that broke when upgrading a codebase from 9.2 to 9.6. Spoiler: it wasn't GHC's fault! The breakage was caused by

  • mtl API change: Module Control.Monad.Except no longer exports liftIO
  • mtl API change: Module Control.Monad.Except no longer exports MonadIO
  • transformers API change: instance for Alternative (Either String) removed
  • base.Prelude: exports liftA2 so import of liftA2 from module Control.Applicative is redundant. This triggers a warning (which causes a failure under -Werror)
  • base16: the API changed
  • GHC: The use of ~ without TypeOperators triggers a warning (which causes a failure under -Werror)

Only one of the five issues arose due to a change in GHC, and that was only a warning, so not actually a breaking change according to the GHC's proposed stability goals.

Here's a second example: a GHC 9.8 breakage inventory, of all breakage that I could discover that users had experienced when upgrading from 9.6 to 9.8. None of them are to do with changes to GHC per se. They are all related to libraries which are bundled with GHC.

As a third example, I am currently migrating a codebase from 8.10 to 9.6. With the exception of shallow subsumption, which I easily avoid by using DeepSubsumption, none of the breakage I have encountered in the migration is related to a change to GHC. GHC does emit some warnings where it previously didn't, for example because the pattern match checker has become more clever, but again they do not count as breaking changes per the proposed stability goals. The biggest headaches I encountered were to do with aeson changing its Object and key types between version 1 and 2, the split of singletons into three separate packages, as well as the base, mtl and transformers API changes listed above.

So GHC per se is simply not currently a major cause of instability in the Haskell ecosystem. The major cause is the package ecosystem. Now, GHC does exacerbate the problem: it forces an upgrade of base (hopefully not for much longer though) and template-haskell, both of which have knock-on effects throughout the ecosystem. I suspect these forced upgrades are the reason that people complain about new GHC releases causing breakage, not because GHC per se breaks anything.

Secondly, I'd like to challenge the notion that enforced stability will improve the situation. To me the idea that using Stable20XX forces you to use only a stable subset of Haskell throughout your package is like putting yourself in straitjacket to avoid banging your elbow; the idea that using Stable20XX forces you to use only other packages that have also used Stable20XX is like forcing yourself to only interact with people in straitjackets in case they bang your elbow. We need to make it easier to do better things, not harder to do worse things.

I would strongly advise against looking at stability from the rigid point of view that we use when we look at mathematical proofs and type systems. Stability is a systems engineering concept, not a mathematical concept. If we try to shoehorn it into a rigid mathematical framework then we won't end up with a form of stability that serves us, instead we will serve stability.

My suggestion would be to go ahead and define a stable subset of Haskell that GHC commits to supporting for a prolonged period, call it Stable20XX, and give it the same status of GHC20XX: allow users to enable it on a per-module basis and don't give it any special status in respect to any other tooling, including cabal and Hackage. Then let's come back in six to twelve months to see how it has played out in practice and iterate from there (I would also welcome backports to earlier GHCs to speed up adoption). Let's not come up with grand designs that force us to bite of more than we can chew.

@simonpj
Copy link
Contributor

simonpj commented Mar 7, 2024

So GHC per se is simply not currently a major cause of instability in the Haskell ecosystem. The major cause is the package ecosystem.

I think you are right, but I would love to hear from others on this point.

Now, GHC does exacerbate the problem: it forces an upgrade of base (hopefully not for much longer though) and template-haskell, both of which have knock-on effects throughout the ecosystem

Yes. To be specific:

  • I think a "re-installable base", seems within reach. I think Ben's notes are the most recent exposition.
  • I am not so sure what to do about Template Haskell. I think that GHC T24021 is the most recent exposition. It needs a champion to drive it forward.

Neither of these conflict with this proposal; but when it comes to where to invest scarce cycles, both are probably more important :-).

To me the idea that using Stable20XX forces you to use only a stable subset of Haskell throughout your package is like putting yourself in straitjacket

I agree. This is what I described as "nanny-ish" above. Let's not.

@Bodigrim
Copy link

Bodigrim commented Mar 8, 2024

So GHC per se is simply not currently a major cause of instability in the Haskell ecosystem. The major cause is the package ecosystem.

I disagree.

  • You can potentially insulate yourself from pretty much every change in package ecosystem (alternative preludes, compatibility packages, etc.).

  • You cannot insulate yourself from changes in the typechecker. Simplified subsumption was the most prominent example, but there were more (e. g., at some point UndecidableInstances stopped implying FlexibleInstances, breaking plenty of packages).

  • You cannot insulate yourself from changes in the optimizer. If your program compiles, but is ten times slower, leaks thunks or takes unreasonable time to build, you are still broken. (That said, the proposal does not seem to promise anything about this aspect?..)

@tomjaguarpaw
Copy link
Contributor

tomjaguarpaw commented Mar 8, 2024

So GHC per se is simply not currently a major cause of instability in the Haskell ecosystem. The major cause is the package ecosystem.

I disagree.

Firstly, perhaps I used terminology in an unclear way. By "not major" I didn't mean "not important" nor "should not be improved". I meant that such breakage only comprises a small minority of the total breakage in the ecosystem.

Perhaps that resolves the disagreement, but if not, then secondly, it's not really a subjective matter where people can agree or disagree. It's an objective empirical matter borne out by practice. Maybe GHC is a major contributor to instability, but that can only be demonstrated by real world observations. The three data points I provided are hard for me to reconcile with GHC being a major contributor to instability but perhaps those data points are not representative. If you can share any real world experience reports of all the breakage that happened during a major version GHC upgrade that would be very helpful.

I think it is very important to determine the major causes of instability and primarily focus on improving those first. Naturally, different streams of work can happen in parallel, but if we expect to be able to improve stability without careful analysis of real world outcomes then we are likely to be disappointed.

@aspiwack
Copy link
Contributor

aspiwack commented Mar 8, 2024

I meant that such breakage only comprises a small minority of the total breakage in the ecosystem […] but perhaps those data points are not representative. If you can share any real world experience reports of all the breakage that happened during a major version GHC upgrade that would be very helpful.

My experience is the same as yours. The GHC bits aren't where I spend time. With one notable exception: the MonadFail transition was quite painful.

@tomjaguarpaw
Copy link
Contributor

the MonadFail transition was quite painful.

But that wasn't a GHC change per se, was it?

@aspiwack
Copy link
Contributor

aspiwack commented Mar 8, 2024

But that wasn't a GHC change per se, was it?

Debatable 🙂 . I'd argue it was: the desugaring of the “do” notation changed, and that's largely what caused issues.

@tomjaguarpaw
Copy link
Contributor

Ah yes, desugaring a partial pattern match picked up a MonadFail constraint where it only picked up a Monad constraint before. So I'd agree it was a GHC change.

@Bodigrim
Copy link

Bodigrim commented Mar 8, 2024

Perhaps that resolves the disagreement, but if not, then secondly, it's not really a subjective matter where people can agree or disagree. It's an objective empirical matter borne out by practice. Maybe GHC is a major contributor to instability, but that can only be demonstrated by real world observations.

I think the cause of disagreement is that you apply open-source lens, where indeed ecosystem churn is a bigger contributor. What I am talking about is an industrial setting, where I can happily fork pretty much every package (e. g., stick to aeson-1.5 and mtl-2.2 indefinitely long). It's unpleasant but totally doable. The only thing I cannot reasonably fork is GHC itself, so if GHC breaks typechecking or optimizer there is no escape hatch currently.

@tomjaguarpaw
Copy link
Contributor

Yes, that's right. I was including open source packages on Hackage in my assessment.

I'm still not sure I fully understand the stability criteria that are important to you, because although you can indeed insulate yourself from breakage in packages by not upgrading the packages you can also insulate yourself from breakage in GHC but not upgrading GHC. If you want a new feature in a newer package you'll have to take the risk of breakage from the upgrade; if you want a new feature in a newer GHC you'll likewise have to take the risk of breakage from the upgrade.

In fact the waters are quite murky because not only can packages make it look like GHC is unstable when it's not (because they don't bump bounds to work with new base) they can also make it look like GHC is stable when it isn't (because they can work around GHC changes with various means such as CPP and compat packages).

In any case, this seems all rather marginal because from #636 (comment) I understand that you and I want the same thing.

@jappeace
Copy link

Hi, Overall I like this idea, here are some things that stood out to me.

From the proposal, it's not clear to me for whom the target audiences are for the various introduced language editions
Stable2024 -> commercial users?
Experimental2024 -> hobby project / art projects? - will this stabilize experimentation?
Latest -> also hobby projects / art projects? - I suppose this would encode the status code
Student2024 -> I guess students or books? Which I think would be similar to Stable2024.

At one point it mentions it shouldn't recommend datakinds to students but that's a bit strange to me. Especially, students have all the time in the world to figure out typelevel programming.
Although I really like the idea of being able to put an alternative error message per target audience.
Perhaps I could even configure it such that: If I'm using servant do recommend datakinds, otherwise never recommend datakinds. 😄

You completely lost me at semantic bundles 😅 Why is this included?

I think it's a great idea to introduce warning control into language extensions. This would make upgrading easier because I clearly remember having to go through an entire warning phase after having solved all library compilation issues or stuff related to simplified subsumption. (In commercial code base you'd have -Werror enabled on CI). This would've prevented that.


I'm sorry if I repeated comments, I don't have time to read through all discussion

@Kleidukos
Copy link

@jappeace The proposal answers your question for Student2024:

Furthermore, any code written in the Student edition is expected to compile with all future versions of GHC for 10 years, meant to echo the expected lifetime of a textbook.

@jappeace
Copy link

@Kleidukos what's the difference from stable then?

@angerman
Copy link
Contributor

angerman commented Mar 25, 2024

Here's an example: @angerman has written a helpful list of everything that broke when upgrading a codebase from 9.2 to 9.6. Spoiler: it wasn't GHC's fault! The breakage was caused by

  • mtl API change: Module Control.Monad.Except no longer exports liftIO
  • mtl API change: Module Control.Monad.Except no longer exports MonadIO
  • transformers API change: instance for Alternative (Either String) removed
  • base.Prelude: exports liftA2 so import of liftA2 from module Control.Applicative is redundant. This triggers a warning (which causes a failure under -Werror)
  • base16: the API changed
  • GHC: The use of ~ without TypeOperators triggers a warning (which causes a failure under -Werror)

Only one of the five issues arose due to a change in GHC, and that was only a warning, so not actually a breaking change according to the GHC's proposed stability goals.

[...]
So GHC per se is simply not currently a major cause of instability in the Haskell ecosystem. The major cause is the package ecosystem.

Just to clarify one point, the above sample was what I consider light breakage, it still took many hours to get sorted properly. Other changes have been much more impactful. Especially if you are trying to upgrade early after the release.

I can't stress this enough: If the next compiler does not accept the same code as the previous compiler, we completely fail at quality control. I cannot run regression tests against 9.10 today. And production the production codebase is stuck on 8.10 (with compatibility up to 9.8 almost everywhere) due to performance regressions since. Not sure if I should cry or laugh at this.

Also mtl, and transfromers, are part of the GHC distribution. For transformers it's even worse as it's a dependency of ghc, which you cannot reinstall, while mtl arguably is only there for Cabal.

The bigger issue though is that because GHC itself normalizes this, it does not set the necessary precedence for downstream. If everything upstream is broken all the time, this signals to downstream that breaking stuff is perfectly acceptable. As a downstream maintainer, what does it buy you to try and not break things, if upstream breaks stuff anyway?

As I've mentioned elsewhere, I do consider -Werror self-inflicted failure, but listed those for completeness sake. If it had only been -Werror.

@mpickering
Copy link
Contributor

@angerman The CLC governs the maintenance of the mtl library, the impact of the mtl-2.3 release was raised with the CLC (haskell/core-libraries-committee#136) by GHC developers who were concerned about the impact on stability.

You are a member of the CLC so it seems that if you disagree with the sentiments on this ticket about why upgrading to mtl-2.3 was the right thing to do then it would be best to raise it on that forum. Likewise, if you object to breaking changes which are approved the GHC steering committee (which you also a member), then GHC developers can do nothing about that, so you should raise an issue there to discuss that.

I don't think it is very useful to make broad sweeping statements such as "everything is broken all of the time", that's not actionable, vague and shows a level of disrespect to GHC contributors who have spent a significant amount of time on stability related issues in the last few years. Creating "perfect" software is an impossible goal which no-one should be held to. One can only hope to incrementally improve things over a sustained period of time. It's very demotivating to actually put work in to improve the situation for those efforts to be ignored or discounted regularly and publicly by prominent members of the community.

@angerman
Copy link
Contributor

@mpickering you will notice that I am doing precisely that since I am on the CLC and SC. I try to be very consistent in this. I will however not refrain from pointing out issues that there are. Yes, we are making progress, and I'm not denying that. Pretending there currently are none would be wrong. We need to acknowledge the defects there are so we can work towards resolving them. If you think I'm discounting efforts, you are misunderstanding me. If you think I should not highlight issues that exists today, because these issues might be resolved in the future, we disagree on that.

@hasufell
Copy link

The CLC governs the maintenance of the mtl library, the impact of the mtl-2.3 release was raised with the CLC (haskell/core-libraries-committee#136) by GHC developers who were concerned about the impact on stability.

We've discussed that in the ticket you linked already:

  • CLC doesn't maintain mtl and can only give suggestions and opinions if the mtl maintainers ask for it
  • CLC can appoint a new maintainer if need be, ensuring good health of all core libraries
  • GHC HQ isn't responsible (IMO) for breaking changes of boot/core libs... they should follow core lib maintainers recommendations
  • but it's responsible for communication and coordination with core library maintainers

I don't think there's a point of making GHC HQ responsible for the whole "GHC bindist" experience.

We rather need to move away from the whole bundling business and establish better boundaries and "pipelines". E.g. bundling is ultimately a job for distributors (stackage is a bundle).

@goldfirere
Copy link
Contributor Author

Been a busy few weeks, followed by a holiday. Back in action here.

@michaelpj

Re counterproposal: Interesting. To me, the key difference between what you've written here and what I've proposed is that your design includes stability information in an artifact, something I had not considered. It allows stability to be opt-out, instead of opt-in. That is, you can have a package be stable "by accident", just by choice of dependencies and language extensions. I think I'd rather stability be opt-in, though: this means that stable package authors are aware of the commitment they are making, leading to a lower likelihood of a package switching from stable to unstable in a future version.

The rest of the counter-proposal just looks like a small UI change: separating the year of the edition from the stability level. I think doing so is sensible, but I don't yet see an advantage to it. I could easily be persuaded here. This counter-proposal also suggests yearly editions, which I think would increase the burden on GHC maintainers.

Have I missed something here?

@mpickering

Yes, all good points. I agree completely that the burden on implementors is long and enduring. My estimation is that the burden will not actually be all that big (that is, a small ongoing tax), but I could easily be wrong here. In answer to your direct questions:

  • How is it verified that a change violates the Stable2024 guarantee?

I'm not imagining any new mechanism here. The existing (nicely beefed up!) procedures will work: the testsuite, head.hackage, early beta testing, code review, etc. The new bit is, essentially, how to react when an incompatibility arises. When we intentionally or unintentionally change behavior, a little debate arises, asking whether we should go with the new behavior or return to the old one or make a proposal or add a warning, etc. With the new stability guarantees in place, this debate will be simpler: we will retain the old behavior with old language editions and get the new one with new language editions. Implementing both behaviors is indeed a burden, but I don't think we'll need new process.

  • Who is responsible for enforcing that GHC MRs are abiding by this change?

Part of the normal code review process. User-facing changes are usually evident in testsuite changes. A good reviewer already routinely considers whether these changes are good or bad. This new guarantee just informs that assessment. I don't see this as being burdensome.

  • If the person identified is a volunteer, then how is it ensured there is a volunteer available for the entire period who is willing and able to timely review all GHC MRs to ensure they uphold the standards?

A valid question, but I don't think a single individual can be expected to be the stability steward. This is a shared responsibility, just like making sure we don't accept the program not "boo". Because the change should be evident in the testsuite, I don't think this adds to the review burden. Specifically, every case I can think of where we've changed behavior in this way has been caught using existing processes; the difference after this proposal would be how we resolve the ensuing debate, not how we start the debate.

@simonpj

It's hard to know how best to handle this ticket vs #635. On the one hand, it's nice to have separate places to debate separate ideas. But on the other, I think this proposal would be pretty meaningless without the big table categorizing everything.

I think the bundles could somewhat usefully be separated out from this proposal. Yet I favor keeping them in, at least until this is submitted to the committee. My reasoning is that, in this proposal, I'm trying to change the way that GHC and its community thinks about language extensions. Up until now, extensions have been a primary part of GHC's interface. I am trying to relegate them to an advanced feature, where more users interact with editions and bundles than extensions.

About the nanny state: It would be problematic if there were no way out. But there is! Just say Experimental. By specifying Stable, users are requesting nannying. It would be boorish not to oblige.

This proposal neither contributes to, no[r] detracts from, [the goal of having code that compiles with GHC(n) compile with GHC(n+k)].

I disagree here; I think it contributes to this goal by giving a mechanism for change. That is, suppose we find a flaw in the instance resolution algorithm in an obscure case with FlexibleInstances. (This happens with some regularity, in practice.) Today, we are stuck: we either must continue with the old behavior (which we think is somehow bad) or we change it (which is unstable). With language editions, we can do both! We keep the old behavior with the old edition and can enable the new behavior in new editions. This "do both" is, to me, the chief burden of this proposal: it means maintaining both behaviors for a number of years. Annoying. But likely possible. (It might be very annoying sometimes, in which case we might decide it's worth breaking the stability guarantee.)

In some sense, I agree with you in that this proposal doesn't change our aspirations toward stability nor gives us new resources. But it does propose a way to resolve stability-centered debates, which I believe will be helpful.

Although we cannot technically deliver on this yet, we should say that our goal is that a language edition also fixes a particular API for Prelude, all base modules, and preferably all Template Haskell modules. This is key for delivering stability; yet the proposal barely mentions the issue.

This is intentional. I agree that we'd need stability in the way you say for this to really deliver. But thinking about language separate from libraries is something we can easily do, and so I've decided to keep these concerns separate. I'll add some text to the proposal about this. Note that the fact that an edition can control all aspects of GHC means that the aspirational goal you seek is technically allowed here.

I plan on adding some commentary about transitive stability, in response to some comments from @michaelpj. I will make sure to reference GR1-3.

Any StableXXXX language edition should depend only on language extensions designated as Stable, [and] not require dependency on the ghc-experimental library

I'm not exactly sure what this means. There's not a notion of a stable extension beyond inclusion in a Stable edition. And we could say that a Stable edition forbids a dependency on ghc-experimental, but I think that will be more adequately handled in an (upcoming) addition to the proposal about transitive stability.

Out of time now. More later.

@angerman
Copy link
Contributor

I disagree here; I think it contributes to this goal by giving a mechanism for change. That is, suppose we find a flaw in the instance resolution algorithm in an obscure case with FlexibleInstances. (This happens with some regularity, in practice.) Today, we are stuck: we either must continue with the old behavior (which we think is somehow bad) or we change it (which is unstable). With language editions, we can do both! We keep the old behavior with the old edition and can enable the new behavior in new editions.

@goldfirere I find this very scary. This implies we have Extensions behavior depend on the language edition. This means it adds to cognitive load when reading code. You not only need to understand the enable language extension, but also it's behaviour wrt to a specific language edition. I'd much rather see Language Extensions evolve with versions. FlexibleInstances, FlexibleInstances2, ...

This should not put a dent into Language Editions itself much. But will provide explicit clarity, instead of implicit.

@michaelpj
Copy link

michaelpj commented Mar 27, 2024

To me, the key difference between what you've written here and what I've proposed is that your design includes stability information in an artifact

The main thing I'm trying to do is thread the needle between the different user personas I'm thinking about:

  • The "rock solid" people need the transitive, global assurance of stability. That's what they want! Anything else just means one of their dependencies will break and then it's back to the grindstone.
  • Most of the other users want a lighter touch, and mostly care about the local property that their code will not break.

I continue to think the current proposal is a bit of a muddle on this front, and I think a guiding light of "language editions are local, stability information is global" might help.

I think I'd rather stability be opt-in, though: this means that stable package authors are aware of the commitment they are making, leading to a lower likelihood of a package switching from stable to unstable in a future version.

Is the case you're worrying about something like this?

  • Package X is accidentally stable
  • Package Y is deliberately stable and thinks it's safe to depend on X
  • Package X breaks stability
  • Woe and tears

That's a worry indeed. I think you're right and we probably need a marker to say "check that this module is transitively stable". How about -XStable? :D Then we have a meaningful difference:

  • A package that uses Stable2024 asserts that it's code is stable
  • A package that additionally uses Stable asserts that it and its dependencies are stable

Plenty of design wiggle room here, but I think something like this could work. I do think it would be helpful to have this distinction on a per-module basis - I wouldn't be surprised if our old, tricky core libraries in particular end up with some stable and some unstable modules.

The rest of the counter-proposal just looks like a small UI change: separating the year of the edition from the stability level. I think doing so is sensible, but I don't yet see an advantage to it. I could easily be persuaded here.

I also propose having no other language editions (except "no edition, YOLO", aka GHCNow). The idea here is that we simplify the way we think about how language editions behave. Compare some hypothetical code in GHC:

-- Any time we check an edition-based behaviour it will look like this
if editionTime < 2024 then <old behaviour> else <new behaviour>

versus

-- Added to stable in 2024, added to student in 2026
-- Is this even complete? maybe there are other cases?
-- The logic is also potentially completely different at every
-- different site
if (stableEdition && editionTime < 2024) || (studentEdition && editionTime < 2026) then <old behaviour> else <new behaviour>

The former approach seems significantly easier for GHC devs to me (I agree a lot with Matt that we have to make this not too painful).

Similarly, if language extension behaviour changes between editions, then users have to do this logic in their head, per-extension. Today, language editions can change behaviour between GHCs, so users still have to do this reasoning, but based on only one variable: the GHC version. My proposal retains this property, but instead the reasoning is based on a different single variable: the year of the language edition.

We can cope with "FlexibleInstances changed slightly in GHC 9.8", I think we can also cope with "FlexibleInstances changed slightly in Stable2024", I'm not sure we can cope with "FlexibleInstances changed slightly in Stable2024, Student2026, and Experimental (in GHC 9.8, not earlier Experimentals)".

This is also my response to Moritz's comment that it's scary for language extensions to change based on edition: we already cope with language extensions changing based on GHC version, so I don't think this is any worse. But we shouldn't make it worse by expanding the dimensionality of the edition matrix. Please let's stay 1-d!

Have I missed something here?

The negative space is meaningful. I'm proposing to do a lot less. Drop the feature bundles, drop the mandatory warnings, just have a single big number that controls the behaviour of the bits of the compiler that affect whether your code compiles.

@adamgundry
Copy link
Contributor

I'm also skeptical that we should make extension behaviour depend directly on language edition. But I think versioning extensions provides a way forward here, because the language edition can select from the available versions of each extension. And equally it will be clear when the extension version has not changed between language editions, which is not clear if the meaning of the extension varies depending on the edition.

Suppose we call what we have FlexibleInstances-1, then for a user who sets GHC2024, enabling FlexibleInstances always gives them FlexibleInstances-1. If we want to make a change to the extension in 2025, we can introduce FlexibleInstances-2, which is initially only available with Experimental when the user explicitly asks for it. Later on we can release GHC2027 which interprets FlexibleInstances to mean FlexibleInstances-2. Crucially, this allows us to develop a change independently of the language edition schedule, and incorporate it into the next edition when it has stabilised.

This should work particularly well in cases where we are adding clearly new functionality to an existing extension (e.g. the planned additions to ExplicitNamespaces). However, there will always be differences in meaning between different compiler versions, not least because of bugs, but also because of the implementation cost of supporting multiple specifications. I don't think we can realistically commit to always maintaining exact compatibility with previous compiler versions, absent a precise specification of every extension. I think versioned extensions can be a useful tool here, but sometimes maintaining older versions of an extension will not be feasible.

@jannikloehn
Copy link

I think i have some ideas for some reasonable reduction of the proposal, but i am not sure if i fully understood all the details and implications of the current state of affairs compared to the proposal.
By my understanding, the ways of configuring the accepted language are as follows:

  • warnings flags:
    • can error (via -Werror)
    • can not change the meaning of a program
    • can warn
    • can be negated (via -Wno-...)
  • language extensions:
    • can error
    • can change the meaning of a program
    • can not warn
    • can be negated (via -XNo...)
    • are allowed to conflict (e.g. specifying -XExtA and -XNoExtA)
    • can enable (imply) other extensions
      • one exception: RebindableSyntax disables ImplicitPrelude
      • negating an extension ExtA that implies another extension ExtB does not negate B. Therefore -XExtA -XNoExtA is not a no-op, but enables ExtB. This behaviour is not specified in the GHC manual (?).
  • language editions (currently):
    • can enable/disable extensions
    • can not enable/disable warning flags
    • can not be negated
    • exclusive (at most/exactly one language edition must be specified)
  • language editions (as proposed):
    • can enable/disable extensions (Y, blank)
    • can enable/disable warning flags (W, E)
    • can not be negated
    • exclusive (at most/exactly one language edition must be specified)
    • can prevent warnings and extensions from being enabled/disabled at any other point (N, W!, E!)
      • it seems to be unspecified whether extensions/warnings that are added to GHC after the addition of an language edition but before the next edition in the corresponding category should be enabled/disabled/forbidden. I think the intent is, that the Stable and Student editions prevent all warnings/extensions added afterwards to be enabled (defaulting to N) while the Experimental and Latest Editions default to some other behabiour (most likely making extensions available, but not on, and making warnings warn, but not error?)
    • can additionally control "almost all" behaviours of GHC
      • from what i can see, this is included mainly to allow the evolution of language extensions in a non-backwards compatible way
      • could of course also do other cool stuff like changing the Prelude etc.
  • bundles:
    • can enable/disable extensions (X, O)
    • can enable/disable warning flags (W, E)
    • can probably (see here) not be negated
    • not exclusive
    • unspecified whether they can conflict, as none of the proposed bundles conflicts with another one

It would be great if someone could confirm whether this list is correct and complete (@goldfire ?)

If the above is correct, my thoughts are:

Language editions (as proposed) are nearly identical to bundles, except they are exclusive, can forcefully prevent a feature from being enabled/disabled and allowing control of unspecified other behaviour of GHC.
If bundles were not allowed to conflict (which seems sensible), a language edition is then just a bundle that conflicts with all other language editions (becoming "exclusive"), that can control other GHC behaviour. Assuming the later is indeed only needed to allow for the backwards-incompatible evolution of language extensions, when versioning extensions as suggested by @adamgundry a language edition would no longer need this power and just become a bundle.

Further thought in this direction would be to figure out which extensions are unlikely to change in a backwards-incompatible way in the future (call these stable extensions, e.g. DeriveFunctor, but probably also stuff like ExplicitNamespaces under proposal #581?) and which extensions might reasonably change in such a way (call these unstable extensions, e.g. OverloadedRecordUpdate). This classification (stable/unstable) should be noted in the documentaion. One can probably suggest a similiar system for warnings.
Then an extension versioning scheme would only be required for stable extensions.
A bundle (aka a language edition) can then be inferred/declared stable, if it only implies stable extensions (and only stable warnings, if that is defined)
Additionally, a package can also be inferred/declared stable, if it does not use unstable extensions/warnings, and neither does any of its (transitive) dependencies.

I apologize if this is to much of a wall-of-text/clutter, i am new to contributing to this kinda stuff.

@jannikloehn
Copy link

jannikloehn commented May 11, 2024

I ended up thinking about this some more:

An (potential) additional benefit of the above procedure is, that it can easily be split into smaller proposals that (i think) each have merit of their own:

Proposal 1: Add bundles that bundle together warning flag and language extensions
Motivation:

  • Basically the motivation given in this proposal
  • Personally, i would love stuff like -XComplete
  • -XComplete could of course be added via an new -Wcomplete warning group. More generally, right now we have bundles that either only enable warnings/the corresponding errors (warning groups, -Werror=group, or only enable extensions (extensions that (only) imply other extensions)

Proposal 2: Explizitly annotate extensions and warnings as (un-)stable and allow declaration/inferring of packages as (un-)stable
Motivation:

Proposal 3: Redefine language editions
Motivation: see this proposal
This adds some bundles (say Stable2024, Student2024 etc.) and/or redefines things like GHC2021 as a bundle.
Bundles that are considered as language editions always conflict with one another.
A plan can be defined for a release schedule of stable language editions (bundles), that will always only enable stable extensions/warnings, which will have guaranteed support for N years.
A language edition could/should include some preferential treatment compared to other bundles, for example default-language in a cabal file or similiar constructs should enable them.

@adamgundry
Copy link
Contributor

@jannikloehn Thanks for thinking this through. I agree that the best way forward here is to make incremental progress where we can reasonably split things up, and that is gradually happening (though the discussion is admittedly quite fragmented). In particular:

  • Extension lifecycle framework proposal (under review) #601 needs to go back to the committee but I think it is nearly agreed. That will give us a framework for classifying extensions as stable/experimental.
  • Alongside Which GHC features are experimental? #635 I have been working on a document and spreadsheet putting together a concrete classification, which I'm trying to align with that framework. That needs to turn into a proper proposal (rather than just a discussion and some drafts), which I'm planning to do at some point, although if anyone wants to help out that would be welcome.
  • I don't think there is yet consensus on the "right" relationship between extensions and warnings. Personally I feel there is a useful distinction between the two (e.g. see Oleg's blog post), but I accept it could be more clearly defined. Bundling warnings into better-defined groups or having language editions change default warning settings makes sense to me in principle, but the details need to be worked out.
  • Similarly, the idea of having the compiler infer stability properties or enforce restrictions on the use of unstable features has been proposed (e.g. Add -experimental flag #617), but in my view we need to nail down the classification first, and I'm not yet convinced we have a fully worked through design for how this should work.

You may also be interested in Simon's stability state of play document which attempts to bring some of these threads together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet