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

[RFC 0137] Nix language versioning #137

Draft
wants to merge 48 commits into
base: master
Choose a base branch
from

Conversation

fricklerhandwerk
Copy link
Contributor

@fricklerhandwerk fricklerhandwerk commented Dec 16, 2022

Introduce a convention to determine which version of the Nix language grammar to use for parsing and evaluating Nix expressions.
Add parameters to the Nix language evaluator, controlling the behavior of deprecation warnings and errors.

Design goals: avoid breaking existing code, prevent inadvertently breaking reproducibility, minimise maintenance burden for implementors and users.

Regardless, changes to the language, especially breaking changes, should remain a rare exception.

Rendered

Reviewers: Please add inline comments adding your arguments as suggestions, ideally one per comment. Resolving these conversations will keep the amount of foreground information tightly limited to what's still relevant.

Readers: No need to inspect closed comments, the considerations and conclusions there can be assumed to be part of the RFC text.

Please, do not add regular comments concerning contents! Otherwise we will lose track quickly.
Reserve regular comments for procedural issues, such as determining shepherds.

This work is sponsored by Antithesis

@fricklerhandwerk fricklerhandwerk force-pushed the rfc-137 branch 5 times, most recently from f1002b3 to 04a6fc1 Compare December 16, 2022 08:44
@piegamesde

This comment was marked as resolved.

the convention indeed does not encourage discipline, as @andir points
out[1], and at the worst would encourage haphazard changes because it
makes them appear harmless, as @tazjin adds [2].

[1]: https://github.com/NixOS/rfcs/pull/137/files#r1050542977
[2]: https://github.com/NixOS/rfcs/pull/137/files#r1050580213
as suggested by @piegamesde
@7c6f434c

This comment was marked as duplicate.

rfcs/0137-nix-language-version.md Outdated Show resolved Hide resolved
rfcs/0137-nix-language-version.md Outdated Show resolved Hide resolved
rfcs/0137-nix-language-version.md Outdated Show resolved Hide resolved
rfcs/0137-nix-language-version.md Outdated Show resolved Hide resolved
rfcs/0137-nix-language-version.md Outdated Show resolved Hide resolved
rfcs/0137-nix-language-version.md Outdated Show resolved Hide resolved
Co-authored-by: piegames <account+github@piegames.de>
@kevincox
Copy link
Contributor

kevincox commented Sep 6, 2023

This RFC is now open for shepherd nominations.

(I think, if this is still intended to be a draft please mark as such)

@kevincox kevincox added the status: open for nominations Open for shepherding team nominations label Sep 6, 2023
@piegamesde
Copy link
Member

piegamesde commented Sep 10, 2023

I'd like to nominate myself as a shepherd.

We had a discussion at Nixcon, and these are the takeaway points we agreed on as I understand them (most are aligned with the current RFC text, although some adjustments will have to be made):

  • Language version declaration can be done with a special keyword, as the first syntax element, on a per file basis
    • This fixes the syntax for declaring language versions, which cannot be changed in the future
    • Comments before the version declaration are allowed too (because of shebangs and stuff), which means the syntax for declaring comments is fixed too. (Technically there are ways around that restriction but honestly who would want to change the comment syntax anyway?)
    • The language version declaration can be made optional if it is instead declared in an external per-project file. This could be done for example in Flakes or in a dedicated mechanism, but this is up to the implementors.
  • The scope of the versioning affects the language's syntax (except for the cases mentioned above), builtins, all operators and types.
    • Values themselves are not versioned, there is no distinction like "v3 str vs v4 str"
  • Backwards compatibility requirements:
    • The system is "zero cost", meaning that no performance overhead must be paid when no legacy Nix files are imported
    • The semantics are simple and fixed, to not hinder alternative implementations
    • Backwards compatibility must not be tightly coupled with the evaluation logic, it must be possible to factor most of it out of the main evaluation code
      • Notably, it must be possible to implement without having to internally tag each value with "its" language version.
  • Backwards compatibility specifics:
    • Code of an old version evaluates to the same result on the new versions for all input values which are legal on the old version.
    • When new value types are added to the language:
      • Passing new values to functions is allowed, as it cannot be prevented anyways due to laziness and composite types (like lists)
      • Any values of unknown type to code from an older Nix version are treated as opaque "external" type (which already exists for things like plugins). Attempts at using them other than passing them around will thus cause type errors.
    • When a value type is removed from the language:
      • The type will always need to exist in the language and evaluator, and it will always be possible to create values of that type by coding in an older Nix version.
      • The language can only be adapted to inhibit the creation of and interaction with such values, effectively rendering them opaque.
    • When the set of values of a type is modified:
      • TODO, we forgot to talk about that (language versioning is one of these things on which you can spend many hours and still forget some edge cases lol)

I am still not sure whether we want to go down the path of versioning the language, and if the benefits are worth the cost. But I am convinced that if we are going to, this is more or less how it will look like.

@piegamesde
Copy link
Member

So, thinking about the "When the set of values of a type is modified" case which is still missing. I see several different approaches to this:

  1. Simply forbid it. The desired effect could still be implemented by adding and removing types, although this would be a bit painful because it reduces cross-version interoperability.
  2. Simply allow it without any special handling. This means that some values of a type might only be created in some versions of the language. The guarantee here is that the builtins and operators must work on all potential values for that type across all affected versions.
  3. Create some rules for introduction and removal of values in the same way as for types.
  • Adding new values: old builtins and operators don't work with them and throw an error when encountering
  • Removing new values: other way around

I am in favor of option 1. until someone can convince me that this scenario actually has some desirable use cases. Otherwise, I simply don't see it being worth the effort handling (and it can always be specified later on when needed AFAICT).

@kevincox
Copy link
Contributor

Thanks for the writeup. I almost entirely agree.

One small thing about value changes. The nice things about these is that they aren't actually part of this decision but part of the actual changes in a language version. This means that we don't actually need to set any rules here because whether or not a value change is a good idea will be discussed at the time that the change is actually made.

That being said I think it is almost certainly going to be a bad idea and don't expect to see literal value types change in the future. (See Python 3 str vs bytes vs unicode for an example.) There would have to be a pretty significant issue that needs addressing and there would need to be lots of care to ensure that it works well with most existing code.

@tazjin
Copy link
Member

tazjin commented Sep 11, 2023

I've been asked to repeat my stance on this, which I've partially explained on this issue, and also in conversations with people. My two primary points are these:

  1. I am opposed to the principle idea of this RFC. Nix has no pressing issues that will be fixed by language changes. Making the language volatile can only lead to more mess, not less. Every reasonable example of a change in the RFC should just be a deprecation warning, with policies applied outside of the scope of the language (e.g. add deprecation warning for a thing, then eventually get rid of it in nixpkgs and block new introductions of that thing in nixpkgs).
  2. It is a bad idea to introduce an additional versioning mechanism. The existing mechanism isn't even used for the breaking language changes in nearly every C++ Nix release! Another mechanism is another axis of pain introduced here.

As the maintainer of tvix-eval, I reserve the right to add specific warnings discouraging the use of a future language versioning mechanism to its evaluator.

@tomberek
Copy link
Contributor

This RFC is now in discussion with the following shepherds: @piegamesde, @sterneseemann (lead), @gabriel-doriath-dohler. Thanks!

as discussed with @​roberth and @​piegamesde

Co-authored-by: Yorick van Pelt <yorick.vanpelt@tweag.io>
@fricklerhandwerk
Copy link
Contributor Author

Updated the RFC text with what we discussed with @piegames and @roberth, based on #137 (comment).

@tazjin – agreed that the Nix ecosystem has more acute issues than evolving the language, and that allowing for more moving parts will make the job of implementors harder. Certainly, many problematic patterns can be removed from plain sight through conventions.

I disagree on the details though:

  • This RFC does not introduce another versioning mechanism, it intends to supersede builtins.langVersion and codify a much more disciplined handling of language changes.
  • Deprecation warnings are in fact part of this RFC, but without proper version handling they won't solve the long-term backwards compatibility problem: Whenever you need old code, you'll have to work around warnings manually, and won't ever be able to mechanically forbid deprecated constructs in contemporary code.
  • This RFC very clearly discourages making the language volatile, to the contrary. I think we should be extremely conservative with the language.

Clearly, implementing the RFC would be a significant undertaking, and at this point developing a prototype seems not to be worth the effort given both the expressed opposition to the proposal and other priorities.

@alyssais noted on some other occasion that it would be reasonable if language implementors had some say in the language specification and evolution (on the merit of having put in the work and thereby being knowledgeable on the subject matter). @Ericson2314 repeatedly pointed out that more competition for implementation quality would benefit the ecosystem. Adding more requirements will certainly not make new evaluators cheaper, and having any specification at all (that is not "all Nixpkgs release branches should evaluate and produce the same store paths as the upstream evaluator of the day") should probably be considered before attempting to change that specification. In any case, I would surely want to have everyone on board who is as deeply involved with the language as Tvix developers, and I take your input very seriously. So thank you @tazjin and @sternenseemann for taking the time to work with us here.

There is also something to be said about the needs of language users though, that is, Nix expression authors, especially the ever-growing number of beginners. Depending on the ecosystem's growth patterns, the scales may still tip in a way that make these particular longer-term considerations become economically viable to pursue further. I hope that the work done here is a useful exploration of both the problem and solution space that can be picked up in the future.

For now I will put the RFC into draft mode. Shepherds, if you agree we can close it and add a concluding statement if mine is not enough.

@fricklerhandwerk fricklerhandwerk marked this pull request as draft October 4, 2023 12:05
@piegamesde
Copy link
Member

I'm fine with closing this RFC, however I see the need for some improvements around the Nix language and its versioning. Specifically, I'd like to see how far we can take our existing language versioning and deprecation infrastructure without having to introduce new per-file language syntax. This is an area that should be explored eventually. For example, I'd like to see more aggressive deprecation of language features that shouldn't be used in new code.

@infinisil infinisil changed the title [RFC 137] Nix language versioning [RFC 0137] Nix language versioning Nov 16, 2023
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/how-to-partially-compensate-for-the-lack-of-static-typing-type-annotations/40139/2

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/nix-team-member-suggests-removing-flakes-data-suggest-it-isnt-a-good-idea-please-remove-the-experimental-flag-instead/54959/96

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

Successfully merging this pull request may close these issues.